UNPKG

siesta-lite

Version:

Stress-free JavaScript unit testing and functional testing tool, works in NodeJS and browsers

216 lines (159 loc) 8.06 kB
/* 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 } } })