diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 155d3a6fd0c..cf6c5ae6d7a 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -292,29 +292,7 @@ exports.plot = function(gd, data, layout, config) { return; } - var subplots = fullLayout._subplots.cartesian; - var modules = fullLayout._modules; - var setPositionsArray = []; - - // position and range calculations for traces that - // depend on each other ie bars (stacked or grouped) - // and boxes (grouped) push each other out of the way - - var subplotInfo, i, j; - - for(j = 0; j < modules.length; j++) { - Lib.pushUnique(setPositionsArray, modules[j].setPositions); - } - - if(setPositionsArray.length) { - for(i = 0; i < subplots.length; i++) { - subplotInfo = fullLayout._plots[subplots[i]]; - - for(j = 0; j < setPositionsArray.length; j++) { - setPositionsArray[j](gd, subplotInfo); - } - } - } + Plots.doSetPositions(gd); // calc and autorange for errorbars Registry.getComponentMethod('errorbars', 'calc')(gd); diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js index 57ac5ba04f4..0ccbc57a4c4 100644 --- a/src/plots/cartesian/index.js +++ b/src/plots/cartesian/index.js @@ -122,15 +122,32 @@ exports.finalizeSubplots = function(layoutIn, layoutOut) { } }; +/** + * Cartesian.plot + * + * @param {DOM div | object} gd + * @param {array | null} (optional) traces + * array of traces indices to plot + * if undefined, plots all cartesian traces, + * if null, plots no traces + * @param {object} (optional) transitionOpts + * transition option object + * @param {function} (optional) makeOnCompleteCallback + * transition make callback function from Plots.transition + */ exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) { var fullLayout = gd._fullLayout; var subplots = fullLayout._subplots.cartesian; var calcdata = gd.calcdata; var i; - // If traces is not provided, then it's a complete replot and missing - // traces are removed - if(!Array.isArray(traces)) { + if(traces === null) { + // this means no updates required, must return here + // so that plotOne doesn't remove the trace layers + return; + } else if(!Array.isArray(traces)) { + // If traces is not provided, then it's a complete replot and missing + // traces are removed traces = []; for(i = 0; i < calcdata.length; i++) traces.push(i); } diff --git a/src/plots/plots.js b/src/plots/plots.js index a6aa8c7ecbd..f80c151d736 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -2202,7 +2202,7 @@ plots.transition = function(gd, data, layout, traces, frameOpts, transitionOpts) // There's nothing to do if this module is not defined: if(!module) continue; - // Don't register the trace as transitioned if it doens't know what to do. + // Don't register the trace as transitioned if it doesn't know what to do. // If it *is* registered, it will receive a callback that it's responsible // for calling in order to register the transition as having completed. if(module.animatable) { @@ -2238,9 +2238,8 @@ plots.transition = function(gd, data, layout, traces, frameOpts, transitionOpts) delete gd.calcdata; plots.supplyDefaults(gd); - plots.doCalcdata(gd); - + plots.doSetPositions(gd); Registry.getComponentMethod('errorbars', 'calc')(gd); return Promise.resolve(); @@ -2265,7 +2264,6 @@ plots.transition = function(gd, data, layout, traces, frameOpts, transitionOpts) var aborted = false; function executeTransitions() { - gd.emit('plotly_transitioning', []); return new Promise(function(resolve) { @@ -2333,6 +2331,9 @@ plots.transition = function(gd, data, layout, traces, frameOpts, transitionOpts) if(hasAxisTransition) { traceTransitionOpts = Lib.extendFlat({}, transitionOpts); traceTransitionOpts.duration = 0; + // This means do not transition traces, + // this happens on layout-only (e.g. axis range) animations + transitionedTraces = null; } else { traceTransitionOpts = transitionOpts; } @@ -2561,6 +2562,30 @@ function clearAxesCalc(axList) { } } +plots.doSetPositions = function(gd) { + var fullLayout = gd._fullLayout; + var subplots = fullLayout._subplots.cartesian; + var modules = fullLayout._modules; + var methods = []; + var i, j; + + // position and range calculations for traces that + // depend on each other ie bars (stacked or grouped) + // and boxes (grouped) push each other out of the way + + for(j = 0; j < modules.length; j++) { + Lib.pushUnique(methods, modules[j].setPositions); + } + if(!methods.length) return; + + for(i = 0; i < subplots.length; i++) { + var subplotInfo = fullLayout._plots[subplots[i]]; + for(j = 0; j < methods.length; j++) { + methods[j](gd, subplotInfo); + } + } +}; + plots.rehover = function(gd) { if(gd._fullLayout._rehover) { gd._fullLayout._rehover(); diff --git a/test/jasmine/tests/animate_test.js b/test/jasmine/tests/animate_test.js index 4c43332de3a..7762963f3e2 100644 --- a/test/jasmine/tests/animate_test.js +++ b/test/jasmine/tests/animate_test.js @@ -1,10 +1,12 @@ var Plotly = require('@lib/index'); var Lib = require('@src/lib'); +var Registry = require('@src/registry'); var Plots = Plotly.Plots; +var d3 = require('d3'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); -var fail = require('../assets/fail_test'); +var failTest = require('../assets/fail_test'); var delay = require('../assets/delay'); var mock = require('@mocks/animation'); @@ -93,7 +95,7 @@ describe('Test animate API', function() { // // This means that you should not expect `.animate` to actually // modify the plot in any way in the tests below. For tests - // involvingnon-faked transitions, see the bottom of this file. + // involving non-faked transitions, see the bottom of this file. // ------------------------------------------------------------ spyOn(Plots, 'transition').and.callFake(function() { @@ -174,11 +176,11 @@ describe('Test animate API', function() { // traces are [0, 1]: expect(args[3]).toEqual([0, 1]); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('rejects if a frame is not found', function(done) { - Plotly.animate(gd, ['foobar'], animOpts).then(fail).then(done, done); + Plotly.animate(gd, ['foobar'], animOpts).then(failTest).then(done, done); }); it('treats objects as frames', function(done) { @@ -186,7 +188,7 @@ describe('Test animate API', function() { Plotly.animate(gd, frame, animOpts).then(function() { expect(Plots.transition.calls.count()).toEqual(1); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('treats a list of objects as frames', function(done) { @@ -203,56 +205,56 @@ describe('Test animate API', function() { expect(Plots.transition.calls.count()).toEqual(2); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('animates all frames if list is null', function(done) { Plotly.animate(gd, null, animOpts).then(function() { verifyFrameTransitionOrder(gd, ['base', 'frame0', 'frame1', 'frame2', 'frame3']); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('animates all frames if list is undefined', function(done) { Plotly.animate(gd, undefined, animOpts).then(function() { verifyFrameTransitionOrder(gd, ['base', 'frame0', 'frame1', 'frame2', 'frame3']); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('animates to a single frame', function(done) { Plotly.animate(gd, ['frame0'], animOpts).then(function() { expect(Plots.transition.calls.count()).toEqual(1); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('animates to an empty list', function(done) { Plotly.animate(gd, [], animOpts).then(function() { expect(Plots.transition.calls.count()).toEqual(0); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('animates to a list of frames', function(done) { Plotly.animate(gd, ['frame0', 'frame1'], animOpts).then(function() { expect(Plots.transition.calls.count()).toEqual(2); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('animates frames by group', function(done) { Plotly.animate(gd, 'even-frames', animOpts).then(function() { expect(Plots.transition.calls.count()).toEqual(2); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('animates frames in the correct order', function(done) { Plotly.animate(gd, ['frame0', 'frame2', 'frame1', 'frame3'], animOpts).then(function() { verifyFrameTransitionOrder(gd, ['frame0', 'frame2', 'frame1', 'frame3']); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('accepts a single animationOpts', function(done) { @@ -260,7 +262,7 @@ describe('Test animate API', function() { var calls = Plots.transition.calls; expect(calls.argsFor(0)[5].duration).toEqual(1.12345); expect(calls.argsFor(1)[5].duration).toEqual(1.12345); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('accepts an array of animationOpts', function(done) { @@ -273,7 +275,7 @@ describe('Test animate API', function() { expect(calls.argsFor(1)[4].duration).toEqual(5.4321); expect(calls.argsFor(0)[5].duration).toEqual(1.123); expect(calls.argsFor(1)[5].duration).toEqual(1.456); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('falls back to animationOpts[0] if not enough supplied in array', function(done) { @@ -286,7 +288,7 @@ describe('Test animate API', function() { expect(calls.argsFor(1)[4].duration).toEqual(2.345); expect(calls.argsFor(0)[5].duration).toEqual(1.123); expect(calls.argsFor(1)[5].duration).toEqual(1.123); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('chains animations as promises', function(done) { @@ -295,7 +297,7 @@ describe('Test animate API', function() { }).then(function() { verifyFrameTransitionOrder(gd, ['frame0', 'frame1', 'frame2', 'frame3']); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('emits plotly_animated before the promise is resolved', function(done) { @@ -306,7 +308,7 @@ describe('Test animate API', function() { Plotly.animate(gd, ['frame0'], animOpts).then(function() { expect(animated).toBe(true); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('emits plotly_animated as each animation in a sequence completes', function(done) { @@ -332,7 +334,7 @@ describe('Test animate API', function() { }).then(function() { expect(test1).toBe(1); expect(test2).toBe(1); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('resolves at the end of each animation sequence', function(done) { @@ -341,7 +343,7 @@ describe('Test animate API', function() { }).then(function() { verifyFrameTransitionOrder(gd, ['frame0', 'frame2', 'frame0', 'frame2', 'frame1', 'frame3']); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); }); } @@ -362,7 +364,7 @@ describe('Test animate API', function() { Plotly.animate(gd, ['frame0', 'frame2', 'frame1', 'frame3'], animOpts).then(function() { verifyFrameTransitionOrder(gd, ['frame3', 'frame1', 'frame2', 'frame0']); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('animates a group in reverse', function(done) { @@ -370,7 +372,7 @@ describe('Test animate API', function() { Plotly.animate(gd, 'even-frames', animOpts).then(function() { verifyFrameTransitionOrder(gd, ['frame2', 'frame0']); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); }); @@ -394,7 +396,7 @@ describe('Test animate API', function() { }).then(function() { verifyFrameTransitionOrder(gd, ['frame1', 'frame2', 'frame3']); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('plays from the start when current frame = last frame', function(done) { @@ -410,7 +412,7 @@ describe('Test animate API', function() { ]); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('animates in reverse starting at the current frame', function(done) { @@ -423,7 +425,7 @@ describe('Test animate API', function() { }).then(function() { verifyFrameTransitionOrder(gd, ['frame1', 'frame0', 'base']); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('plays in reverse from the end when current frame = first frame', function(done) { @@ -440,7 +442,7 @@ describe('Test animate API', function() { ]); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); }); @@ -467,7 +469,7 @@ describe('Test animate API', function() { Plotly.animate(gd, ['frame2'], Lib.extendFlat(animOpts, {mode: 'immediate'})).then(function() { expect(interrupted).toBe(true); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('queues successive animations', function(done) { @@ -486,7 +488,7 @@ describe('Test animate API', function() { Plotly.animate(gd, 'odd-frames', {transition: {duration: 16}}).then(delay(10)).then(function() { expect(ends).toEqual(1); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('an empty list with immediate dumps previous frames', function(done) { @@ -494,7 +496,7 @@ describe('Test animate API', function() { Plotly.animate(gd, [], {mode: 'immediate'}).then(function() { expect(Plots.transition.calls.count()).toEqual(1); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('animates groups in the correct order', function(done) { @@ -502,7 +504,7 @@ describe('Test animate API', function() { Plotly.animate(gd, 'odd-frames', animOpts).then(function() { verifyFrameTransitionOrder(gd, ['frame0', 'frame2', 'frame1', 'frame3']); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('drops queued frames when immediate = true', function(done) { @@ -510,7 +512,7 @@ describe('Test animate API', function() { Plotly.animate(gd, 'odd-frames', Lib.extendFlat(animOpts, {mode: 'immediate'})).then(function() { verifyFrameTransitionOrder(gd, ['frame0', 'frame1', 'frame3']); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('animates frames and groups in sequence', function(done) { @@ -518,12 +520,12 @@ describe('Test animate API', function() { Plotly.animate(gd, ['frame0', 'frame2', 'frame1', 'frame3'], animOpts).then(function() { verifyFrameTransitionOrder(gd, ['frame0', 'frame2', 'frame0', 'frame2', 'frame1', 'frame3']); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('rejects when an animation is interrupted', function(done) { var interrupted = false; - Plotly.animate(gd, ['frame0', 'frame1'], animOpts).then(fail, function() { + Plotly.animate(gd, ['frame0', 'frame1'], animOpts).then(failTest, function() { interrupted = true; }); @@ -531,7 +533,7 @@ describe('Test animate API', function() { expect(interrupted).toBe(true); verifyFrameTransitionOrder(gd, ['frame0', 'frame2']); verifyQueueEmpty(gd); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); }); @@ -550,7 +552,7 @@ describe('Test animate API', function() { frame: {duration: 1} }).then(function() { expect(frames).toEqual(['frame0', 'frame1', null, null]); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); }); @@ -567,7 +569,7 @@ describe('Test animate API', function() { // Transition timing: expect(Plots.transition.calls.argsFor(0)[5].duration).toEqual(50); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('limits the transition duration to <= frame duration (matching per-config)', function(done) { @@ -583,7 +585,7 @@ describe('Test animate API', function() { expect(Plots.transition.calls.argsFor(0)[5].duration).toEqual(50); expect(Plots.transition.calls.argsFor(1)[5].duration).toEqual(40); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); }); }); @@ -615,7 +617,7 @@ describe('Animate API details', function() { {frame: {redraw: true, duration: dur}, transition: {duration: dur}} ).then(function() { expect(redraws).toBe(1); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('forces a relayout after layout animations', function(done) { @@ -633,7 +635,7 @@ describe('Animate API details', function() { expect(relayouts).toBe(1); expect(restyles).toBe(0); expect(redraws).toBe(0); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('triggers plotly_animated after a single layout animation', function(done) { @@ -679,7 +681,7 @@ describe('Animate API details', function() { }).then(function() { // Confirm the result: expect(gd.data[0].x).toEqual([8, 7, 6]); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('ignores null and undefined frames', function(done) { @@ -694,7 +696,7 @@ describe('Animate API details', function() { // Check unused frames did not affect the current frame: expect(gd._fullLayout._currentFrame).toEqual('frame0'); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('null frames should not break everything', function(done) { @@ -703,7 +705,7 @@ describe('Animate API details', function() { Plotly.animate(gd, null, { frame: {duration: 0}, transition: {duration: 0} - }).catch(fail).then(done); + }).catch(failTest).then(done); }); }); @@ -747,7 +749,7 @@ describe('Animating multiple axes', function() { expect(gd._fullLayout.yaxis.range).toEqual([2, 3]); expect(gd._fullLayout.yaxis2.range).toEqual([1, 2]); }) - .catch(fail) + .catch(failTest) .then(done); }); }); @@ -778,7 +780,7 @@ describe('non-animatable fallback', function() { }], {frame: {duration: 0}}); }).then(function() { expect(gd.data[0].y).toEqual([6, 4, 5]); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); }); @@ -812,7 +814,7 @@ describe('animating scatter traces', function() { }], {transition: {duration: 0}, frame: {duration: 0, redraw: false}}); }).then(function() { expect(trace.node().style.opacity).toEqual('0.1'); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('computes calcdata correctly when transforms are present', function(done) { @@ -834,6 +836,81 @@ describe('animating scatter traces', function() { return Plotly.animate(gd, ['frame2'], {frame: {duration: 200, redraw: false}}); }).then(function() { expect(gd.calcdata[0][0].y).toEqual(3); - }).catch(fail).then(done); + }).catch(failTest).then(done); + }); + + it('should animate axis ranges using the less number of steps', function(done) { + // sanity-check that scatter points and bars are still there + function _assertNodeCnt() { + var gd3 = d3.select(gd); + expect(gd3.select('.scatterlayer').selectAll('.point').size()) + .toBe(3, '# of pts on graph'); + expect(gd3.select('.barlayer').selectAll('.point').size()) + .toBe(3, '# of bars on graph'); + } + + // assert what Cartesian.transitionAxes does + function getSubplotTranslate() { + var sp = d3.select(gd).select('.subplot.xy > .plot'); + return sp.attr('transform') + .split('translate(')[1].split(')')[0] + .split(',') + .map(Number); + } + + Plotly.plot(gd, [{ + y: [1, 2, 1] + }, { + type: 'bar', + y: [2, 1, 2] + }]) + .then(function() { + var t = getSubplotTranslate(); + expect(t[0]).toBe(80, 'subplot translate[0]'); + expect(t[1]).toBe(100, 'subplot translate[1]'); + + spyOn(gd._fullData[0]._module.basePlotModule, 'transitionAxes').and.callThrough(); + spyOn(gd._fullData[0]._module, 'plot').and.callThrough(); + spyOn(gd._fullData[1]._module, 'plot').and.callThrough(); + spyOn(Registry, 'call').and.callThrough(); + + var promise = Plotly.animate('graph', { + layout: { + xaxis: {range: [0.45, 0.55]}, + yaxis: {range: [0.45, 0.55]} + } + }, { + transition: {duration: 500}, + frame: {redraw: false} + }); + + setTimeout(function() { + _assertNodeCnt(); + var t = getSubplotTranslate(); + expect(t[0]).toBeLessThan(80, 'subplot translate[0]'); + expect(t[1]).toBeLessThan(100, 'subplot translate[1]'); + }, 100); + + return promise; + }) + .then(function() { + _assertNodeCnt(); + var t = getSubplotTranslate(); + expect(t[0]).toBe(80, 'subplot translate[0]'); + expect(t[1]).toBe(100, 'subplot translate[1]'); + + // the only redraw should occur during Cartesian.transitionAxes, + // where Registry.call('relayout') is called leading to a _module.plot call + expect(gd._fullData[0]._module.basePlotModule.transitionAxes).toHaveBeenCalledTimes(1); + expect(gd._fullData[0]._module.plot).toHaveBeenCalledTimes(1); + expect(gd._fullData[1]._module.plot).toHaveBeenCalledTimes(1); + expect(Registry.call).toHaveBeenCalledTimes(1); + + var calls = Registry.call.calls.all(); + expect(calls.length).toBe(1, 'just one Registry.call call'); + expect(calls[0].args[0]).toBe('relayout', 'called Registry.call with'); + }) + .catch(failTest) + .then(done); }); }); diff --git a/test/jasmine/tests/transition_test.js b/test/jasmine/tests/transition_test.js index 949aaaa14c3..60095b95815 100644 --- a/test/jasmine/tests/transition_test.js +++ b/test/jasmine/tests/transition_test.js @@ -5,7 +5,7 @@ var plotApiHelpers = require('@src/plot_api/helpers'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); -var fail = require('../assets/fail_test'); +var failTest = require('../assets/fail_test'); var delay = require('../assets/delay'); var mock = require('@mocks/animation'); @@ -36,7 +36,7 @@ function runTests(transitionDuration) { .then(delay(20)) .then(function() { expect(Date.now() - t1).toBeGreaterThan(transitionDuration); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('emits plotly_transitioning on transition start', function(done) { @@ -49,7 +49,7 @@ function runTests(transitionDuration) { .then(delay(20)) .then(function() { expect(beginTransitionCnt).toBe(1); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('emits plotly_transitioned on transition end', function(done) { @@ -62,7 +62,7 @@ function runTests(transitionDuration) { .then(delay(20)) .then(function() { expect(trEndCnt).toEqual(1); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('transitions an annotation', function(done) { @@ -90,7 +90,7 @@ function runTests(transitionDuration) { expect(p1[0]).not.toEqual(p2[0]); expect(p1[1]).not.toEqual(p2[1]); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('transitions an image', function(done) { @@ -125,7 +125,7 @@ function runTests(transitionDuration) { // Test that the image element identity has not: expect(e1).toBe(e2); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); it('transitions a shape', function(done) { @@ -182,7 +182,7 @@ function runTests(transitionDuration) { expect(d3).toEqual(d2); expect(s3).not.toEqual(s2); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); @@ -218,7 +218,7 @@ function runTests(transitionDuration) { target: 'x', value: 10 })]); - }).catch(fail).then(done); + }).catch(failTest).then(done); }); // This doesn't really test anything that the above tests don't cover, but it combines @@ -252,7 +252,7 @@ function runTests(transitionDuration) { expect(endCnt).toEqual(3); }) .then(checkNoneRunning) - .catch(fail).then(done); + .catch(failTest).then(done); }); }); }