Excuse my English as it is my second language. So I have a function being called to animated a "reboot" of a CLI interface. This animation is quite long and calls several other more basic animations which are, in turn, placed in the queue of animations to be processed. I have tried just adding all of the animations separately when a command is entered and that works perfectly, however, I would prefer if I could use a function to do it instead because I will be calling the reboot animation quite often. Currently, I am using a class which contains these function which might explain its unusual notation.
So here is the queue processing function:
start_animating(){
try{
if (animation_done){
// if previous animation is done executing
g_e("input").onkeypress = function(event){return main.key_checker(event);};
// briefly set the input box back to its default
var y = this.animations.shift();
// get the latest animation to run
y.func(y.args);
// run the function with the args provided
}else{
//if an animation is running, disallow input from user
g_e("input").onkeypress = function(event){if(event.keyCode == 13){
g_e("input").value = "";return false;}};
}
}
catch(e){}
}
Here is the interval where the processing happens:
var that = this;
setInterval(function(){that.start_animating();}, 1);
Then here is the reboot animation:
animation_reboot(object){
//takes an object as args
this.add_animation(this.clear_console, {});
//adds the clear_console function to the processing queue with no args clearing the div named console
this.add_animation(this.animation_fade, {element:"startUp"});
// adds the fade in / out animation with args element = startUp
this.add_animation(this.plbl, {message:"initiliazing boot sequence............|reticulating squids....^^^^^^|dereticulating squids....^^^^^^|deregulating squids....^^^^^^|reregulating squids....^^^^^^|squids prepared...^^^^^^",element:"console", speed:75});
// adds the print letter by letter animation to queue ^ is a equivelant to a sleep and | to a <br>
this.add_animation(this.clear_console, {});
//clears console again
this.add_animation(this.plbl, {message:"SquidOS.^^^^^^.^^^^^^.^^^^^^ Booted!^^^^^^^^^^^^^^", element:"console", speed:75})
//print letter by letter
this.add_animation(this.clear_console, {});
// clears console again
this.add_animation(this.plbl, {message:"Awating command. Type help for a list of commands.", speed:75, element:"console"});
// print letter by letter
}
The problem occurs when I call:
this.add_animation(this.animation_reboot, {});
Expected behavior would be for the reboot animation to be added to the queue then all the animations associated with the reboot animation to be added and processed.
However, the actual behavior is that the no animations from the reboot animation are being processed. I have tried calling just the reboot animation which causes it to work, but when I try adding the reboot animation to the queue it is not called / the sub animations are not called.
I can edit in other parts of the script / HTML as needed, but I didn't really want to waste anyone's time searching through the entire script. Thanks for any help / advice you can offer.
So here is the issue that I'm trying to solve with casperjs and mocha. I'm trying to test an element's text value on a page to see if its updating over a period of... 5-10 seconds. The idea being I grab the value, push it in to an array, wait 500ms and repeat until the array has 20 items. Thats about 10 seconds. Then run underscore/lodash's _.uniq function on the array and test that the array length is > 1.
The problem I'm running in to is that mocha is not waiting for this to complete because stating the test is a success/failure. I thought I could increase mocha's timeout but this has not made any difference. Please see the code below. I've commented it for readability.
it('has elements whose values update', function () {
// 20 seconds, which should be plenty of time
this.timeout(20000);
casper.then(function() {
// The test array
var values = [],
// So we can stop the intervals
intervalId;
function getValue () {
// Grab element's text value
var value = casper.evaluate(function () { return $('#element').text(); });
// Push in to our test array
values.push(value);
// 20 * 500ms == 10 seconds or 10000ms
// When set to 500ms, this block never runs. The test passes before it has a chance to
if (values.length === 20) {
// Stop it from checking the value any further
clearInterval(intervalId);
// Test to see we've had more than one unique value in the 10 seconds
expect(_.uniq(values).length).to.be.gt(1);
}
}
// Wait for the value on the page to populate
// It defaults to '-' when the page loads
casper.waitFor(function () {
return this.evaluate(function () {
return $('#element').text() !== '-';
});
// Start the check with a delay of 500ms between each check
}, function then() {
intervalId = setInterval(getValue, 500);
});
});
});
With the interval value set at 500ms I get 2-3 element values in values before mocha moves on to the next test. Even odder is when I console.log(values) they are printing on screen AFTER mocha as determined the test passed. The reason is that values.length never gets to 10 so the expect call is never called. The test is assumed to be passing. Here is the test output at 500ms interval:
Dashboard
✓ has elements whose values update (202ms)
Values: ["20,832,022"]
Values: ["20,832,022","20,832,372"]
Values: ["20,832,022","20,832,372","20,832,722"]
✓ has the page title of leads (41ms)
2 passing (11s)
It passes even though there isn't 20 items. It never checks it due to a timeout somewhere. Here is the output with 50ms interval:
Dashboard
✓ has elements whose values update (341ms)
Values: ["20,400,667"]
Values: ["20,400,667","20,400,718"]
Values: ["20,400,667","20,400,718","20,400,718"]
Values: ["20,400,667","20,400,718","20,400,718","20,400,769"]
Values: ["20,400,667","20,400,718","20,400,718","20,400,769","20,400,769"]
Values: ["20,400,667","20,400,718","20,400,718","20,400,769","20,400,769","20,400,820"]
Values: ["20,400,667","20,400,718","20,400,718","20,400,769","20,400,769","20,400,820","20,400,820"]
Values: ["20,400,667","20,400,718","20,400,718","20,400,769","20,400,769","20,400,820","20,400,820","20,400,871"]
Values: ["20,400,667","20,400,718","20,400,718","20,400,769","20,400,769","20,400,820","20,400,820","20,400,871","20,400,871"]
Values: ["20,400,667","20,400,718","20,400,718","20,400,769","20,400,769","20,400,820","20,400,820","20,400,871","20,400,871","20,400,922"]
Final Values: ["20,400,667","20,400,718","20,400,718","20,400,769","20,400,769","20,400,820","20,400,820","20,400,871","20,400,871","20,400,922"]
✓ has the page title of leads (41ms)
2 passing (8s)
I get more with the 50ms but that's only a half second of testing. Some of the other values on the page take longer to update to this is not viable.
I've tried passing the done callback to the it statement but mocha ignores it and doesn't wait for it to be called.
Is this a limitation of the tools or am I using them wrong?
I have tried using the done callback using the method below.
it('has elements whose values update', function (done) {
and
expect(_.uniq(values).length).to.be.gt(1);
done();
It still ignores that I've marked the test as async. At 500ms it still passes without getting to the if statement or done call. At 50ms it throws this error:
done() called multiple times
I am using mocha-casperjs. Could this be affecting it?
It seems that mocha-casperjs doesn't use the default done. It knows that the test step is complete because it uses CasperJS' control flow. In your case, you break out of the control flow by calling getValue through a setInterval.
It would be better to refactor your code to use recursive calls to getValue like this:
function getValue () {
// Grab element's text value
var value = this.evaluate(function () { return $('#element').text(); });
// Push in to our test array
values.push(value);
// 20 * 500ms == 10 seconds or 10000ms
// When set to 500ms, this block never runs. The test passes before it has a chance to
if (values.length === 20) {
// Test to see we've had more than one unique value in the 10 seconds
expect(_.uniq(values).length).to.be.gt(1);
} else {
this.wait(500, getValue);
}
}
// Wait for the value on the page to populate
// It defaults to '-' when the page loads
casper.waitFor(function () {
return this.evaluate(function () {
return $('#element').text() !== '-';
});
// Start the check with a delay of 500ms between each check
}, function then() {
this.wait(500, getValue);
});
This makes getValue a casper step.
Another solution without much refactoring is letting a second waitFor run along side of the broken control flow. This needs a semi-global variable someGlobalVariable. Maybe the intervalId can be used for this, but it's probably better to use someGlobalVariable = false; at the top.
intervalId = setInterval(getValue, 500);
this.waitFor(function check(){
return someGlobalVariable;
}, function then(){
// do something else
}, null, 20000);
and let it stop with
expect(_.uniq(values).length).to.be.gt(1);
someGlobalVariable = true;
I'm creating a content rotator in jQuery. 5 items total. Item 1 fades in, pauses 10 seconds, fades out, then item 2 fades in. Repeat.
Simple enough. Using setTimeout I can call a set of functions that create a loop and will repeat the process indefinitely.
I now want to add the ability to interrupt this rotator at any time by clicking on a navigation element to jump directly to one of the content items.
I originally started going down the path of pinging a variable constantly (say every half second) that would check to see if a navigation element was clicked and, if so, abandon the loop, then restart the loop based on the item that was clicked.
The challenge I ran into was how to actually ping a variable via a timer. The solution is to dive into JavaScript closures...which are a little over my head but definitely something I need to delve into more.
However, in the process of that, I came up with an alternative option that actually seems to be better performance-wise (theoretically, at least). I have a sample running here:
http://jsbin.com/uxupi/14
(It's using console.log so have fireBug running)
Sample script:
$(document).ready(function(){
var loopCount = 0;
$('p#hello').click(function(){
loopCount++;
doThatThing(loopCount);
})
function doThatOtherThing(currentLoopCount) {
console.log('doThatOtherThing-'+currentLoopCount);
if(currentLoopCount==loopCount){
setTimeout(function(){doThatThing(currentLoopCount)},5000)
}
}
function doThatThing(currentLoopCount) {
console.log('doThatThing-'+currentLoopCount);
if(currentLoopCount==loopCount){
setTimeout(function(){doThatOtherThing(currentLoopCount)},5000);
}
}
})
The logic being that every click of the trigger element will kick off the loop passing into itself a variable equal to the current value of the global variable. That variable gets passed back and forth between the functions in the loop.
Each click of the trigger also increments the global variable so that subsequent calls of the loop have a unique local variable.
Then, within the loop, before the next step of each loop is called, it checks to see if the variable it has still matches the global variable. If not, it knows that a new loop has already been activated so it just ends the existing loop.
Thoughts on this? Valid solution? Better options? Caveats? Dangers?
UPDATE:
I'm using John's suggestion below via the clearTimeout option.
However, I can't quite get it to work. The logic is as such:
var slideNumber = 0;
var timeout = null;
function startLoop(slideNumber) {
//... code is here to do stuff here to set up the slide based on slideNumber...
slideFadeIn()
}
function continueCheck() {
if (timeout != null) {
// cancel the scheduled task.
clearTimeout(timeout);
timeout = null;
return false;
} else {
return true;
}
};
function slideFadeIn() {
if (continueCheck){
// a new loop hasn't been called yet so proceed...
$mySlide.fadeIn(fade, function() {
timeout = setTimeout(slideFadeOut,display);
});
}
};
function slideFadeOut() {
if (continueCheck){
// a new loop hasn't been called yet so proceed...
slideNumber=slideNumber+1;
$mySlide.fadeOut(fade, function() {
//... code is here to check if I'm on the last slide and reset to #1...
timeout = setTimeout(function(){startLoop(slideNumber)},100);
});
}
};
startLoop(slideNumber);
The above kicks of the looping.
I then have navigation items that, when clicked, I want the above loop to stop, then restart with a new beginning slide:
$(myNav).click(function(){
clearTimeout(timeout);
timeout = null;
startLoop(thisItem);
})
If I comment out 'startLoop...' from the click event, it, indeed, stops the initial loop. However, if I leave that last line in, it doesn't actually stop the initial loop. Why? What happens is that both loops seem to run in parallel for a period.
So, when I click my navigation, clearTimeout is called, which clears it.
What you should do is save the handle returned by setTimeout and clear it with clearTimeout to interrupt the rotator.
var timeout = null;
function doThatThing() {
/* Do that thing. */
// Schedule next call.
timeout = setTimeout(doThatOtherThing, 5000);
}
function doThatOtherThing() {
/* Do that other thing. */
// Schedule next call.
timeout = setTimeout(doThatThing, 5000);
}
function interruptThings() {
if (timeout != null) {
// Never mind, cancel the scheduled task.
clearTimeout(timeout);
timeout = null;
}
}
When a navigation element is clicked simply call interruptThings(). The nice part is that it will take effect immediately and you don't need to do any polling or anything else complicated.