@@ -9,49 +9,55 @@ var delay = require('../assets/delay');
99
1010var mock = require ( '@mocks/animation' ) ;
1111
12- function runTests ( duration ) {
13- describe ( 'Test animate API (frame duration = ' + duration + ')' , function ( ) {
14- 'use strict' ;
12+ describe ( 'Test animate API' , function ( ) {
13+ 'use strict' ;
1514
16- var gd , mockCopy ;
17- var transOpts = { frameduration : duration } ;
15+ var gd , mockCopy ;
1816
19- function verifyQueueEmpty ( gd ) {
20- expect ( gd . _transitionData . _frameQueue . length ) . toEqual ( 0 ) ;
21- }
17+ function verifyQueueEmpty ( gd ) {
18+ expect ( gd . _transitionData . _frameQueue . length ) . toEqual ( 0 ) ;
19+ }
2220
23- function verifyFrameTransitionOrder ( gd , expectedFrames ) {
24- var calls = PlotlyInternal . transition . calls ;
21+ function verifyFrameTransitionOrder ( gd , expectedFrames ) {
22+ var calls = PlotlyInternal . transition . calls ;
2523
26- expect ( calls . count ( ) ) . toEqual ( expectedFrames . length ) ;
24+ expect ( calls . count ( ) ) . toEqual ( expectedFrames . length ) ;
2725
28- for ( var i = 0 ; i < calls . count ( ) ; i ++ ) {
29- expect ( calls . argsFor ( i ) [ 1 ] ) . toEqual (
30- gd . _transitionData . _frameHash [ expectedFrames [ i ] ] . data
31- ) ;
32- }
26+ for ( var i = 0 ; i < calls . count ( ) ; i ++ ) {
27+ expect ( calls . argsFor ( i ) [ 1 ] ) . toEqual (
28+ gd . _transitionData . _frameHash [ expectedFrames [ i ] ] . data
29+ ) ;
3330 }
31+ }
3432
35- beforeEach ( function ( done ) {
36- gd = createGraphDiv ( ) ;
33+ beforeEach ( function ( done ) {
34+ gd = createGraphDiv ( ) ;
3735
38- mockCopy = Lib . extendDeep ( { } , mock ) ;
39-
40- spyOn ( PlotlyInternal , 'transition' ) . and . callFake ( function ( ) {
41- // Transition's fake behaviro is to resolve after a short period of time:
42- return Promise . resolve ( ) . then ( delay ( duration ) ) ;
43- } ) ;
36+ mockCopy = Lib . extendDeep ( { } , mock ) ;
4437
45- Plotly . plot ( gd , mockCopy . data , mockCopy . layout ) . then ( function ( ) {
46- Plotly . addFrames ( gd , mockCopy . frames ) ;
47- } ) . then ( done ) ;
38+ spyOn ( PlotlyInternal , 'transition' ) . and . callFake ( function ( ) {
39+ // Transition's fake behaviro is to resolve after a short period of time:
40+ return Promise . resolve ( ) . then ( delay ( 5 ) ) ;
4841 } ) ;
4942
50- afterEach ( function ( ) {
51- // *must* purge between tests otherwise dangling async events might not get cleaned up properly:
52- Plotly . purge ( gd ) ;
53- destroyGraphDiv ( ) ;
54- } ) ;
43+ Plotly . plot ( gd , mockCopy . data , mockCopy . layout ) . then ( function ( ) {
44+ Plotly . addFrames ( gd , mockCopy . frames ) ;
45+ } ) . then ( done ) ;
46+ } ) ;
47+
48+ afterEach ( function ( ) {
49+ // *must* purge between tests otherwise dangling async events might not get cleaned up properly:
50+ Plotly . purge ( gd ) ;
51+ destroyGraphDiv ( ) ;
52+ } ) ;
53+
54+ for ( var i = 0 ; i < 2 ; i ++ ) {
55+ // Run tests for 0ms and 30ms duration:
56+ runTests ( 10 + 30 * i ) ;
57+ }
58+
59+ function runTests ( duration ) {
60+ var transOpts = { frameduration : duration } ;
5561
5662 it ( 'animates to a frame' , function ( done ) {
5763 Plotly . animate ( gd , [ 'frame0' ] , { duration : 1.2345 } ) . then ( function ( ) {
@@ -83,54 +89,51 @@ function runTests(duration) {
8389 Plotly . animate ( gd , [ 'foobar' ] , transOpts ) . then ( fail ) . then ( done , done ) ;
8490 } ) ;
8591
86- it ( 'animates to a single frame ' , function ( done ) {
87- Plotly . animate ( gd , [ 'frame0' ] , transOpts ) . then ( function ( ) {
88- expect ( PlotlyInternal . transition . calls . count ( ) ) . toEqual ( 1 ) ;
92+ it ( 'animates all frames if list is null ' , function ( done ) {
93+ Plotly . animate ( gd , null , transOpts ) . then ( function ( ) {
94+ verifyFrameTransitionOrder ( gd , [ 'base' , 'frame0' , 'frame1' , 'frame2' , 'frame3' ] ) ;
8995 verifyQueueEmpty ( gd ) ;
9096 } ) . catch ( fail ) . then ( done ) ;
9197 } ) ;
9298
93- it ( 'animates to a list of frames ' , function ( done ) {
94- Plotly . animate ( gd , [ 'frame0' , 'frame1' ] , transOpts ) . then ( function ( ) {
95- expect ( PlotlyInternal . transition . calls . count ( ) ) . toEqual ( 2 ) ;
99+ it ( 'animates all frames if list is undefined ' , function ( done ) {
100+ Plotly . animate ( gd , undefined , transOpts ) . then ( function ( ) {
101+ verifyFrameTransitionOrder ( gd , [ 'base' , 'frame0' , 'frame1' , 'frame2' , 'frame3' ] ) ;
96102 verifyQueueEmpty ( gd ) ;
97103 } ) . catch ( fail ) . then ( done ) ;
98104 } ) ;
99105
100- it ( 'animates frames by group ' , function ( done ) {
101- Plotly . animate ( gd , 'even-frames' , transOpts ) . then ( function ( ) {
102- expect ( PlotlyInternal . transition . calls . count ( ) ) . toEqual ( 2 ) ;
106+ it ( 'animates to a single frame ' , function ( done ) {
107+ Plotly . animate ( gd , [ 'frame0' ] , transOpts ) . then ( function ( ) {
108+ expect ( PlotlyInternal . transition . calls . count ( ) ) . toEqual ( 1 ) ;
103109 verifyQueueEmpty ( gd ) ;
104110 } ) . catch ( fail ) . then ( done ) ;
105111 } ) ;
106112
107- it ( 'animates groups in the correct order' , function ( done ) {
108- Plotly . animate ( gd , 'even-frames' , transOpts ) ;
109- Plotly . animate ( gd , 'odd-frames' , transOpts ) . then ( function ( ) {
110- verifyFrameTransitionOrder ( gd , [ 'frame0' , 'frame2' , 'frame1' , 'frame3' ] ) ;
113+ it ( 'animates to an empty list' , function ( done ) {
114+ Plotly . animate ( gd , [ ] , transOpts ) . then ( function ( ) {
115+ expect ( PlotlyInternal . transition . calls . count ( ) ) . toEqual ( 0 ) ;
111116 verifyQueueEmpty ( gd ) ;
112117 } ) . catch ( fail ) . then ( done ) ;
113118 } ) ;
114119
115- it ( 'drops queued frames when immediate = true' , function ( done ) {
116- Plotly . animate ( gd , 'even-frames' , transOpts ) ;
117- Plotly . animate ( gd , 'odd-frames' , transOpts , { immediate : true } ) . then ( function ( ) {
118- verifyFrameTransitionOrder ( gd , [ 'frame0' , 'frame1' , 'frame3' ] ) ;
120+ it ( 'animates to a list of frames' , function ( done ) {
121+ Plotly . animate ( gd , [ 'frame0' , 'frame1' ] , transOpts ) . then ( function ( ) {
122+ expect ( PlotlyInternal . transition . calls . count ( ) ) . toEqual ( 2 ) ;
119123 verifyQueueEmpty ( gd ) ;
120124 } ) . catch ( fail ) . then ( done ) ;
121125 } ) ;
122126
123- it ( 'animates frames in the correct order ' , function ( done ) {
124- Plotly . animate ( gd , [ 'frame0' , 'frame2' , 'frame1' , 'frame3' ] , transOpts ) . then ( function ( ) {
125- verifyFrameTransitionOrder ( gd , [ 'frame0' , 'frame2' , 'frame1' , 'frame3' ] ) ;
127+ it ( 'animates frames by group ' , function ( done ) {
128+ Plotly . animate ( gd , 'even-frames' , transOpts ) . then ( function ( ) {
129+ expect ( PlotlyInternal . transition . calls . count ( ) ) . toEqual ( 2 ) ;
126130 verifyQueueEmpty ( gd ) ;
127131 } ) . catch ( fail ) . then ( done ) ;
128132 } ) ;
129133
130- it ( 'animates frames and groups in sequence' , function ( done ) {
131- Plotly . animate ( gd , 'even-frames' , transOpts ) ;
134+ it ( 'animates frames in the correct order' , function ( done ) {
132135 Plotly . animate ( gd , [ 'frame0' , 'frame2' , 'frame1' , 'frame3' ] , transOpts ) . then ( function ( ) {
133- verifyFrameTransitionOrder ( gd , [ 'frame0' , 'frame2' , 'frame0' , 'frame2' , ' frame1', 'frame3' ] ) ;
136+ verifyFrameTransitionOrder ( gd , [ 'frame0' , 'frame2' , 'frame1' , 'frame3' ] ) ;
134137 verifyQueueEmpty ( gd ) ;
135138 } ) . catch ( fail ) . then ( done ) ;
136139 } ) ;
@@ -204,18 +207,23 @@ function runTests(duration) {
204207 } ) . catch ( fail ) . then ( done ) ;
205208 } ) ;
206209
207- it ( 'rejects when an animation is interrupted' , function ( done ) {
208- var interrupted = false ;
209- Plotly . animate ( gd , [ 'frame0' , 'frame1' ] , transOpts ) . then ( fail , function ( ) {
210- interrupted = true ;
211- } ) ;
212-
213- Plotly . animate ( gd , [ 'frame2' ] , transOpts , { immediate : true } ) . then ( function ( ) {
214- expect ( interrupted ) . toBe ( true ) ;
215- verifyFrameTransitionOrder ( gd , [ 'frame0' , 'frame2' ] ) ;
210+ it ( 'resolves at the end of each animation sequence' , function ( done ) {
211+ Plotly . animate ( gd , 'even-frames' , transOpts ) . then ( function ( ) {
212+ return Plotly . animate ( gd , [ 'frame0' , 'frame2' , 'frame1' , 'frame3' ] , transOpts ) ;
213+ } ) . then ( function ( ) {
214+ verifyFrameTransitionOrder ( gd , [ 'frame0' , 'frame2' , 'frame0' , 'frame2' , 'frame1' , 'frame3' ] ) ;
216215 verifyQueueEmpty ( gd ) ;
217216 } ) . catch ( fail ) . then ( done ) ;
218217 } ) ;
218+ }
219+
220+ // The tests above use promises to ensure ordering, but the tests below this call Plotly.animate
221+ // without chaining promises which would result in race conditions. This is not invalid behavior,
222+ // but it doesn't ensure proper ordering and completion, so these must be performed with finite
223+ // duration. Stricly speaking, these tests *do* involve race conditions, but the finite duration
224+ // prevents that from causing problems.
225+ describe ( 'Calling Plotly.animate synchronously in series' , function ( ) {
226+ var transOpts = { frameduration : 30 } ;
219227
220228 it ( 'emits plotly_animationinterrupted when an animation is interrupted' , function ( done ) {
221229 var interrupted = false ;
@@ -231,6 +239,7 @@ function runTests(duration) {
231239 } ) . catch ( fail ) . then ( done ) ;
232240 } ) ;
233241
242+
234243 it ( 'queues successive animations' , function ( done ) {
235244 var starts = 0 ;
236245 var ends = 0 ;
@@ -243,27 +252,75 @@ function runTests(duration) {
243252 expect ( starts ) . toEqual ( 1 ) ;
244253 } ) ;
245254
246- Plotly . animate ( gd , 'even-frames' , transOpts ) ;
247- Plotly . animate ( gd , 'odd-frames' , transOpts ) . then ( delay ( 10 ) ) . then ( function ( ) {
255+ Plotly . animate ( gd , 'even-frames' , { duration : 16 } ) ;
256+ Plotly . animate ( gd , 'odd-frames' , { duration : 16 } ) . then ( delay ( 10 ) ) . then ( function ( ) {
248257 expect ( ends ) . toEqual ( 1 ) ;
249258 verifyQueueEmpty ( gd ) ;
250259 } ) . catch ( fail ) . then ( done ) ;
251260 } ) ;
252261
253- it ( 'resolves at the end of each animation sequence' , function ( done ) {
254- Plotly . animate ( gd , 'even-frames' , transOpts ) . then ( function ( ) {
255- return Plotly . animate ( gd , [ 'frame0' , 'frame2' , 'frame1' , 'frame3' ] , transOpts ) ;
256- } ) . then ( function ( ) {
262+ it ( 'an empty list with immediate dumps previous frames' , function ( done ) {
263+ Plotly . animate ( gd , [ 'frame0' , 'frame1' ] , { frameduration : 50 } ) ;
264+ Plotly . animate ( gd , [ ] , null , { immediate : true } ) . then ( function ( ) {
265+ expect ( PlotlyInternal . transition . calls . count ( ) ) . toEqual ( 1 ) ;
266+ verifyQueueEmpty ( gd ) ;
267+ } ) . catch ( fail ) . then ( done ) ;
268+ } ) ;
269+
270+ it ( 'animates groups in the correct order' , function ( done ) {
271+ Plotly . animate ( gd , 'even-frames' , transOpts ) ;
272+ Plotly . animate ( gd , 'odd-frames' , transOpts ) . then ( function ( ) {
273+ verifyFrameTransitionOrder ( gd , [ 'frame0' , 'frame2' , 'frame1' , 'frame3' ] ) ;
274+ verifyQueueEmpty ( gd ) ;
275+ } ) . catch ( fail ) . then ( done ) ;
276+ } ) ;
277+
278+ it ( 'drops queued frames when immediate = true' , function ( done ) {
279+ Plotly . animate ( gd , 'even-frames' , transOpts ) ;
280+ Plotly . animate ( gd , 'odd-frames' , transOpts , { immediate : true } ) . then ( function ( ) {
281+ verifyFrameTransitionOrder ( gd , [ 'frame0' , 'frame1' , 'frame3' ] ) ;
282+ verifyQueueEmpty ( gd ) ;
283+ } ) . catch ( fail ) . then ( done ) ;
284+ } ) ;
285+
286+ it ( 'animates frames and groups in sequence' , function ( done ) {
287+ Plotly . animate ( gd , 'even-frames' , transOpts ) ;
288+ Plotly . animate ( gd , [ 'frame0' , 'frame2' , 'frame1' , 'frame3' ] , transOpts ) . then ( function ( ) {
257289 verifyFrameTransitionOrder ( gd , [ 'frame0' , 'frame2' , 'frame0' , 'frame2' , 'frame1' , 'frame3' ] ) ;
258290 verifyQueueEmpty ( gd ) ;
259291 } ) . catch ( fail ) . then ( done ) ;
260292 } ) ;
293+
294+ it ( 'rejects when an animation is interrupted' , function ( done ) {
295+ var interrupted = false ;
296+ Plotly . animate ( gd , [ 'frame0' , 'frame1' ] , transOpts ) . then ( fail , function ( ) {
297+ interrupted = true ;
298+ } ) ;
299+
300+ Plotly . animate ( gd , [ 'frame2' ] , transOpts , { immediate : true } ) . then ( function ( ) {
301+ expect ( interrupted ) . toBe ( true ) ;
302+ verifyFrameTransitionOrder ( gd , [ 'frame0' , 'frame2' ] ) ;
303+ verifyQueueEmpty ( gd ) ;
304+ } ) . catch ( fail ) . then ( done ) ;
305+ } ) ;
261306 } ) ;
262- }
263307
264- for ( var i = 0 ; i < 2 ; i ++ ) {
265- // Set a duration:
266- var d = 30 * i ;
308+ it ( 'animates reasonably even when transition duration >> frame duration' , function ( done ) {
309+ var starts = 0 ;
310+ var ends = 0 ;
311+
312+ gd . on ( 'plotly_animating' , function ( ) {
313+ starts ++ ;
314+ } ) . on ( 'plotly_animated' , function ( ) {
315+ ends ++ ;
316+ } ) ;
317+
318+ Plotly . animate ( gd , [ 'frame0' , 'frame1' ] , { duration : 200 , frameduration : 20 } ) . then ( function ( ) {
319+ expect ( starts ) . toEqual ( 1 ) ;
320+ expect ( ends ) . toEqual ( 1 ) ;
321+ expect ( PlotlyInternal . transition . calls . count ( ) ) . toEqual ( 2 ) ;
322+ verifyQueueEmpty ( gd ) ;
323+ } ) . catch ( fail ) . then ( done ) ;
324+ } ) ;
267325
268- runTests ( d ) ;
269- }
326+ } ) ;
0 commit comments