Skip to content

Commit 0f2d918

Browse files
committed
improve assertSpies
- make testing `invocationOrder` possible, along side "did get called" vs "did not get called" checks - adapt assertSpies calls that checked arguments from multiple calls of a given spy, now each "expectations" item corresponds to one function call
1 parent bead43f commit 0f2d918

File tree

1 file changed

+159
-84
lines changed

1 file changed

+159
-84
lines changed

test/jasmine/tests/transition_test.js

Lines changed: 159 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -296,33 +296,117 @@ describe('Plotly.react transitions:', function() {
296296
});
297297
}
298298

299-
function assertSpies(msg, exps) {
299+
/**
300+
* assertSpies
301+
*
302+
* @param {string} _msg : base message string
303+
* @param {array of arrays} _exps : expectations
304+
*
305+
* assertSpies('test', [
306+
* [<spied-on {object}>, <method name {string}>, `value`].
307+
* [<spied-on {object}>, <method name {string}>, <args {array}>].
308+
* ....
309+
* [<spied-on {object}>, <method name {string}>, <0 or 1 {number}>]
310+
* ]);
311+
*
312+
* - expectations items must be placed in the order in which they are invoked
313+
* - to test that a certain spy didn't get called use `0` as value
314+
* - to test that a certain spy did get called w/o checking its args use `1` as value
315+
*/
316+
function assertSpies(_msg, _exps) {
317+
var msg = function(i, exp, extra) {
318+
return [_msg, 'Item #' + i, '@method ' + exp[1], (extra || '')].join(' | ');
319+
};
320+
321+
// check `_exps` structure and if "did (not) get called"
322+
var failEarly = false;
323+
_exps.forEach(function(exp, i) {
324+
var spy = exp[0];
325+
var methodName = exp[1];
326+
var val = exp[2];
327+
var calls = spy[methodName].calls;
328+
var didGetCalled = calls.count() > 0;
329+
var expectingACall;
330+
331+
if(Array.isArray(val)) {
332+
expectingACall = true;
333+
} else if(val === 0 || val === 1) {
334+
expectingACall = Boolean(val);
335+
} else {
336+
fail(_msg + '- Wrong arguments for assertSpies');
337+
}
338+
339+
expect(didGetCalled).toBe(expectingACall, msg(i, exp, 'did (not) get called'));
340+
failEarly = didGetCalled !== expectingACall;
341+
});
342+
343+
if(failEarly) {
344+
return fail(_msg + '- Wrong calls, assertSpies early fail');
345+
}
346+
347+
// filter out `exps` items with `value=0`
348+
var exps = _exps.filter(function(exp) {
349+
var val = exp[2];
350+
return val !== 0;
351+
});
352+
353+
// find list of spies to assert (N.B. dedupe using `invocationOrder`)
354+
var actuals = [];
355+
var seen = {};
300356
exps.forEach(function(exp) {
301-
var calls = exp[0][exp[1]].calls;
302-
var cnt = calls.count();
303-
304-
if(Array.isArray(exp[2])) {
305-
expect(cnt).toBe(exp[2].length, msg);
306-
307-
var allArgs = calls.allArgs();
308-
allArgs.forEach(function(args, i) {
309-
args.forEach(function(a, j) {
310-
var e = exp[2][i][j];
311-
if(Lib.isPlainObject(a) || Array.isArray(a)) {
312-
expect(a).toEqual(e, msg);
313-
} else if(typeof a === 'function') {
314-
expect('function').toBe(e, msg);
315-
} else {
316-
expect(a).toBe(e, msg);
317-
}
318-
});
357+
var spy = exp[0];
358+
var methodName = exp[1];
359+
var calls = spy[methodName].calls;
360+
if(calls.count()) {
361+
calls.all().forEach(function(c) {
362+
var k = c.invocationOrder;
363+
if(!seen[k]) {
364+
actuals.push([spy, methodName, c.args, k]);
365+
seen[k] = 1;
366+
}
319367
});
320-
} else if(typeof exp[2] === 'number') {
321-
expect(cnt).toBe(exp[2], msg);
322-
} else {
323-
fail('wrong arguments for assertSpies');
324368
}
325369
});
370+
actuals.sort(function(a, b) { return a[3] - b[3]; });
371+
372+
// sanity check
373+
if(actuals.length !== exps.length) {
374+
return fail(_msg + '- Something went wrong when building "actual" callData list');
375+
}
376+
377+
for(var i = 0; i < exps.length; i++) {
378+
var exp = exps[i];
379+
var actual = actuals[i];
380+
var val = exp[2];
381+
var args = actual[2];
382+
383+
if(actual[0] !== exp[0] || actual[1] !== exp[1]) {
384+
fail(_msg + '- Item #' + i + ' with method "' + exp[1] + '" is out-of-order');
385+
continue;
386+
}
387+
388+
if(Array.isArray(val)) {
389+
// assert function arguments
390+
expect(args.length).toBe(val.length, msg(i, exp, '# of args'));
391+
392+
for(var j = 0; j < args.length; j++) {
393+
var arg = args[j];
394+
var e = val[j];
395+
var msgj = msg(i, exp, 'arg #' + j);
396+
397+
if(Lib.isPlainObject(arg)) {
398+
expect(arg).withContext(msgj + ' (obj check)').toEqual(e);
399+
} else if(Array.isArray(arg)) {
400+
expect(arg).withContext(msgj + ' (arr check)').toEqual(e);
401+
} else if(typeof arg === 'function') {
402+
expect('function').toBe(e, msgj + ' (fn check)');
403+
} else {
404+
expect(arg).toBe(e, msgj);
405+
}
406+
}
407+
}
408+
}
409+
326410
resetSpyCounters();
327411
}
328412

@@ -362,7 +446,7 @@ describe('Plotly.react transitions:', function() {
362446
})
363447
.then(function() {
364448
assertSpies('with *transition* set and changes', [
365-
[Plots, 'transitionFromReact', 1],
449+
[Plots, 'transitionFromReact', 1]
366450
]);
367451
})
368452
.catch(failTest)
@@ -547,7 +631,7 @@ describe('Plotly.react transitions:', function() {
547631
.then(function() {
548632
assertSpies('redraw required', [
549633
[Plots, 'transitionFromReact', 1],
550-
[Registry, 'call', [['redraw', gd]]]
634+
[Registry, 'call', ['redraw', gd]]
551635
]);
552636
})
553637
.catch(failTest)
@@ -588,7 +672,7 @@ describe('Plotly.react transitions:', function() {
588672
[Plots, 'transitionFromReact', 1],
589673
[gd._fullLayout._basePlotModules[0], 'transitionAxes', 1],
590674
// one _module.plot call from the relayout at end of axis transition
591-
[Registry, 'call', [['relayout', gd, {'xaxis.range': [-2, 2]}]]],
675+
[Registry, 'call', ['relayout', gd, {'xaxis.range': [-2, 2]}]],
592676
[gd._fullLayout._basePlotModules[0], 'plot', 1],
593677
]);
594678
})
@@ -597,18 +681,16 @@ describe('Plotly.react transitions:', function() {
597681
layout.xaxis.range = [-1, 1];
598682
return Plotly.react(gd, data, layout);
599683
})
684+
.then(delay(20))
600685
.then(function() {
601686
assertSpies('both trace and layout transitions', [
602687
[Plots, 'transitionFromReact', 1],
603688
[gd._fullLayout._basePlotModules[0], 'transitionAxes', 1],
604-
[Registry, 'call', [['relayout', gd, {'xaxis.range': [-1, 1]}]]],
605-
[gd._fullLayout._basePlotModules[0], 'plot', [
606-
// one instantaneous transition options to halt
607-
// other trace transitions (if any)
608-
[gd, null, {duration: 0, easing: 'cubic-in-out', ordering: 'layout first'}, 'function'],
609-
// one _module.plot call from the relayout at end of axis transition
610-
[gd]
611-
]],
689+
// one instantaneous transition options to halt other trace transitions (if any)
690+
[gd._fullLayout._basePlotModules[0], 'plot', [gd, null, {duration: 0, easing: 'cubic-in-out', ordering: 'layout first'}, 'function']],
691+
// one _module.plot call from the relayout at end of axis transition
692+
[Registry, 'call', ['relayout', gd, {'xaxis.range': [-1, 1]}]],
693+
[gd._fullLayout._basePlotModules[0], 'plot', [gd]]
612694
]);
613695
})
614696
.then(function() {
@@ -621,14 +703,12 @@ describe('Plotly.react transitions:', function() {
621703
.then(function() {
622704
assertSpies('both trace and layout transitions under *ordering:traces first*', [
623705
[Plots, 'transitionFromReact', 1],
624-
[gd._fullLayout._basePlotModules[0], 'plot', [
625-
// one smooth transition
626-
[gd, [0], {duration: 10, easing: 'cubic-in-out', ordering: 'traces first'}, 'function'],
627-
// one by relayout call at the end of instantaneous axis transition
628-
[gd]
629-
]],
706+
// one smooth transition
707+
[gd._fullLayout._basePlotModules[0], 'plot', [gd, [0], {duration: 10, easing: 'cubic-in-out', ordering: 'traces first'}, 'function']],
708+
// one by relayout call at the end of instantaneous axis transition
630709
[gd._fullLayout._basePlotModules[0], 'transitionAxes', 1],
631-
[Registry, 'call', [['relayout', gd, {'xaxis.range': [-2, 2]}]]]
710+
[Registry, 'call', ['relayout', gd, {'xaxis.range': [-2, 2]}]],
711+
[gd._fullLayout._basePlotModules[0], 'plot', [gd]]
632712
]);
633713
})
634714
.catch(failTest)
@@ -730,13 +810,10 @@ describe('Plotly.react transitions:', function() {
730810
assertSpies('must transition autoranged axes, not the traces', [
731811
[Plots, 'transitionFromReact', 1],
732812
[gd._fullLayout._basePlotModules[0], 'transitionAxes', 1],
733-
[gd._fullLayout._basePlotModules[0], 'plot', [
734-
// one instantaneous transition options to halt
735-
// other trace transitions (if any)
736-
[gd, null, {duration: 0, easing: 'cubic-in-out', ordering: 'layout first'}, 'function'],
737-
// one _module.plot call from the relayout at end of axis transition
738-
[gd]
739-
]],
813+
// one instantaneous transition options to halt other trace transitions (if any)
814+
[gd._fullLayout._basePlotModules[0], 'plot', [gd, null, {duration: 0, easing: 'cubic-in-out', ordering: 'layout first'}, 'function']],
815+
// one _module.plot call from the relayout at end of axis transition
816+
[gd._fullLayout._basePlotModules[0], 'plot', [gd]],
740817
]);
741818
assertAxAutorange('axes are now autorange:false', false);
742819
})
@@ -748,10 +825,8 @@ describe('Plotly.react transitions:', function() {
748825
assertSpies('transition just traces, as now axis ranges are set', [
749826
[Plots, 'transitionFromReact', 1],
750827
[gd._fullLayout._basePlotModules[0], 'transitionAxes', 0],
751-
[gd._fullLayout._basePlotModules[0], 'plot', [
752-
// called from Plots.transitionFromReact
753-
[gd, [0], {duration: 10, easing: 'cubic-in-out', ordering: 'layout first'}, 'function'],
754-
]],
828+
// called from Plots.transitionFromReact
829+
[gd._fullLayout._basePlotModules[0], 'plot', [gd, [0], {duration: 10, easing: 'cubic-in-out', ordering: 'layout first'}, 'function']]
755830
]);
756831
assertAxAutorange('axes are still autorange:false', false);
757832
})
@@ -795,36 +870,34 @@ describe('Plotly.react transitions:', function() {
795870
})
796871
.then(function() {
797872
assertSpies('both trace and layout transitions', [
873+
// xaxis call to _storeDirectGUIEdit from doAutoRange
874+
[Registry, 'call', ['_storeDirectGUIEdit', gd.layout, gd._fullLayout._preGUI, {
875+
'xaxis.range': [-0.12852664576802508, 2.128526645768025],
876+
'xaxis.autorange': true
877+
}]],
878+
// yaxis call to _storeDirectGUIEdit from doAutoRange
879+
[Registry, 'call', ['_storeDirectGUIEdit', gd.layout, gd._fullLayout._preGUI, {
880+
'yaxis.range': [9.26751592356688, 20.73248407643312],
881+
'yaxis.autorange': true
882+
}]],
883+
798884
[Plots, 'transitionFromReact', 1],
799885
[gd._fullLayout._basePlotModules[0], 'transitionAxes', 1],
800-
[Registry, 'call', [
801-
// xaxis call to _storeDirectGUIEdit from doAutoRange
802-
['_storeDirectGUIEdit', gd.layout, gd._fullLayout._preGUI, {
803-
'xaxis.range': [-0.12852664576802508, 2.128526645768025],
804-
'xaxis.autorange': true
805-
}],
806-
// yaxis call to _storeDirectGUIEdit from doAutoRange
807-
['_storeDirectGUIEdit', gd.layout, gd._fullLayout._preGUI, {
808-
'yaxis.range': [9.26751592356688, 20.73248407643312],
809-
'yaxis.autorange': true
810-
}],
811-
['relayout', gd, {
812-
'yaxis.range': [9.26751592356688, 20.73248407643312]
813-
}],
814-
// xaxis call #2 to _storeDirectGUIEdit from doAutoRange,
815-
// as this axis is still autorange:true
816-
['_storeDirectGUIEdit', gd.layout, gd._fullLayout._preGUI, {
817-
'xaxis.range': [-0.12852664576802508, 2.128526645768025],
818-
'xaxis.autorange': true
819-
}],
820-
]],
821-
[gd._fullLayout._basePlotModules[0], 'plot', [
822-
// one instantaneous transition options to halt
823-
// other trace transitions (if any)
824-
[gd, null, {duration: 0, easing: 'cubic-in-out', ordering: 'layout first'}, 'function'],
825-
// one _module.plot call from the relayout at end of axis transition
826-
[gd]
827-
]]
886+
887+
// one instantaneous transition options to halt other trace transitions (if any)
888+
[gd._fullLayout._basePlotModules[0], 'plot', [gd, null, {duration: 0, easing: 'cubic-in-out', ordering: 'layout first'}, 'function']],
889+
890+
// one _module.plot call from the relayout at end of axis transition
891+
[Registry, 'call', ['relayout', gd, {
892+
'yaxis.range': [9.26751592356688, 20.73248407643312]
893+
}]],
894+
// xaxis call #2 to _storeDirectGUIEdit from doAutoRange,
895+
// as this axis is still autorange:true
896+
[Registry, 'call', ['_storeDirectGUIEdit', gd.layout, gd._fullLayout._preGUI, {
897+
'xaxis.range': [-0.12852664576802508, 2.128526645768025],
898+
'xaxis.autorange': true
899+
}]],
900+
[gd._fullLayout._basePlotModules[0], 'plot', [gd]]
828901
]);
829902
assertAxAutorange('y-axis is now autorange:false', false);
830903
})
@@ -1002,8 +1075,9 @@ describe('Plotly.react transitions:', function() {
10021075
var msg = 'transition into data2';
10031076
assertSpies(msg, [
10041077
[Plots, 'transitionFromReact', 1],
1005-
[gd._fullLayout._basePlotModules[0], 'plot', 2],
1006-
[Registry, 'call', 1],
1078+
[gd._fullLayout._basePlotModules[0], 'plot', 1],
1079+
[Registry, 'call', ['redraw', gd]],
1080+
[gd._fullLayout._basePlotModules[0], 'plot', 1],
10071081
[gd._fullLayout._basePlotModules[0], 'transitionAxes', 0]
10081082
]);
10091083

@@ -1015,8 +1089,9 @@ describe('Plotly.react transitions:', function() {
10151089
var msg = 'transition back to data1';
10161090
assertSpies(msg, [
10171091
[Plots, 'transitionFromReact', 1],
1018-
[Registry, 'call', 1],
1019-
[gd._fullLayout._basePlotModules[0], 'plot', 2],
1092+
[gd._fullLayout._basePlotModules[0], 'plot', 1],
1093+
[Registry, 'call', ['redraw', gd]],
1094+
[gd._fullLayout._basePlotModules[0], 'plot', 1],
10201095
[gd._fullLayout._basePlotModules[0], 'transitionAxes', 0]
10211096
]);
10221097

0 commit comments

Comments
 (0)