siesta-lite
Version:
Stress-free JavaScript unit testing and functional testing tool, works in NodeJS and browsers
236 lines (179 loc) • 8.84 kB
HTML
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>The source code</title>
<link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="../resources/prettify/prettify.js"></script>
<style type="text/css">
.highlight { display: block; background-color: #ddd; }
</style>
<script type="text/javascript">
function highlight() {
document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
}
</script>
</head>
<body onload="prettyPrint(); highlight();">
<pre class="prettyprint lang-js">/*
Siesta 5.6.1
Copyright(c) 2009-2022 Bryntum AB
https://bryntum.com/contact
https://bryntum.com/products/siesta/license
*/
Class('Siesta.Util.Queue', {
has : {
// array of Objects, each containing arbitrary data about queue step. Possibly keys:
// `processor` - an individual processor function for this step
// can also be provided for whole queue
// will receive the: (stepData, index, queue)
// `isAsync` - when provided, the `next` function will be also embedded,
// which should be called manually
// `interval` - the delay after step (except for asynchronous)
steps : Joose.I.Array,
interval : 100,
callbackDelay : 0,
// setTimeout
deferer : { required : true },
// clearTimeout - only required when "abort" is planned / possible
deferClearer : null,
processor : null,
processorScope : null,
currentTimeout : null,
callback : null,
scope : null,
isAborted : false,
observeTest : null,
currentDelayStepId : null,
isDestroyed : false
},
methods : {
// step is an object with
// {
// processor : func,
// processorScope : obj,
// next : func (in case of async step, will be populated by queue)
// }
addStep : function (stepData) {
this.addSyncStep(stepData)
},
addSyncStep : function (stepData) {
this.steps.push(stepData)
},
addAsyncStep : function (stepData) {
stepData.isAsync = true
this.steps.push(stepData)
},
addDelayStep : function (delayMs) {
var origSetTimeout = this.deferer;
var me = this;
this.addAsyncStep({
processor : function(data) {
me.currentDelayStepId = origSetTimeout(data.next, delayMs || 500);
}
});
},
run : function (callback, scope) {
this.callback = callback
this.scope = scope
// abort the queue, if the provided test instance has finalized (probably because of exception)
this.observeTest && this.observeTest.on('teststop', function () { this.abort(true) }, this, { single : true })
this.doSteps(this.steps.slice(), callback, scope)
},
abort : function (ignoreCallback) {
if (this.isDestroyed) return
this.isAborted = true
var deferClearer = this.deferClearer
if (!deferClearer) throw "Need `deferClearer` to be able to `abort` the queue"
deferClearer(this.currentDelayStepId);
deferClearer(this.currentTimeout)
if (!ignoreCallback) this.callback.call(this.scope || this)
this.destroy()
},
doSteps : function (steps, callback, scope) {
this.currentTimeout = null
var me = this
var deferer = this.deferer
var step = steps.shift()
if (this.isAborted) return
if (step) {
// Normally, the `doSteps` is called recursively for every step in the chain
// but, steps may complete synchronously, which means, stack will grow
// since some version, FF has smaller stack size than other browsers
// and it starts behaving unstable when stack grows
// because of that, we perform a special check if step has completed synchronously
// and processing the next step in the same `doStep` context (in the loop), avoiding recursion
// if `doOneStep` has returned `true`, then step has completed synchronously
// and the flow did not recurse into `doSteps`
// in this case we continue processing to the next step
while (this.doOneStep(step, steps, callback, scope) && !this.isAborted) {
if (steps.length)
step = steps.shift()
else {
this.doSteps(steps, callback, scope)
break;
}
}
} else {
if (callback)
if (this.callbackDelay)
deferer(function () {
if (!me.isAborted) { callback.call(scope || this); me.destroy() }
}, this.callbackDelay)
else {
callback.call(scope || this)
me.destroy()
}
}
},
doOneStep : function (step, steps, callback, scope) {
var me = this
var deferer = this.deferer
var processor = step.processor || this.processor
var processorScope = step.processorScope || this.processorScope
var index = this.steps.length - steps.length - 1
if (!processor) throw new Error("No process function found for step: " + index)
if (step.isAsync) {
var stepHasCompletedSynchronously = false
var processorHasCompleted = false
var next = step.next = function () {
// if at this point `processorHasCompleted` is still `false`, that means that "next" function
// has been called before the `processor` function has returned, and thus, step has completed
// synchronously
// see the comment in `doSteps` why we treat this case differently
if (!processorHasCompleted)
stepHasCompletedSynchronously = true
else
me.doSteps(steps, callback, scope)
}
// processor should call `next` to continue
processor.call(processorScope || me, step, index, this, next)
processorHasCompleted = true
if (stepHasCompletedSynchronously) return true
} else {
processor.call(processorScope || me, step, index, this)
if (this.isAborted) return
var interval = step.hasOwnProperty('interval') ? step.interval : me.interval
if (interval)
this.currentTimeout = deferer(function () {
me.doSteps(steps, callback, scope)
}, interval)
else
me.doSteps(steps, callback, scope)
}
},
// help garbage collector to release the memory
destroy : function () {
if (this.isDestroyed) return
this.callback = this.observeTest = this.deferer = this.deferClearer = null
this.processor = this.processorScope = null
// cleanup paranoya, this shouldn't matter in general, since "next" here is from the same context
for (var i = 0; i < this.steps.length; i++) this.steps[ i ].next = null
this.steps = null
this.isDestroyed = true
}
}
})
</pre>
</body>
</html>