mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Added new resizeChecker, and tests
This commit is contained in:
parent
9b1dcd8c25
commit
12493cddd1
11 changed files with 778 additions and 27 deletions
|
@ -9,7 +9,7 @@
|
|||
"bluebird": "~2.0.7",
|
||||
"connect": "~2.19.5",
|
||||
"event-stream": "~3.1.5",
|
||||
"expect.js": "~0.2.0",
|
||||
"expect.js": "~0.3.1",
|
||||
"grunt": "~0.4.5",
|
||||
"grunt-contrib-clean": "~0.5.0",
|
||||
"grunt-contrib-compress": "~0.9.1",
|
||||
|
|
68
src/kibana/components/reflow_watcher.js
Normal file
68
src/kibana/components/reflow_watcher.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
define(function (require) {
|
||||
return function ReflowWatcherService(Private, $rootScope, $http) {
|
||||
var angular = require('angular');
|
||||
var $ = require('jquery');
|
||||
var _ = require('lodash');
|
||||
|
||||
var EventEmitter = Private(require('factories/events'));
|
||||
var $body = $(document.body);
|
||||
var $window = $(window);
|
||||
|
||||
var MOUSE_EVENTS = 'mouseup';
|
||||
var WINDOW_EVENTS = 'resize';
|
||||
|
||||
_(ReflowWatcher).inherits(EventEmitter);
|
||||
/**
|
||||
* Watches global activity which might hint at a change in the content, which
|
||||
* in turn provides a hint to resizers that they should check their size
|
||||
*/
|
||||
function ReflowWatcher() {
|
||||
ReflowWatcher.Super.call(this);
|
||||
|
||||
// bound version of trigger that can be used as a handler
|
||||
this.trigger = _.bind(this.trigger, this);
|
||||
this._emitReflow = _.bind(this._emitReflow, this);
|
||||
|
||||
// list of functions to call that will unbind our watchers
|
||||
this._unwatchers = [
|
||||
$rootScope.$watchCollection(function () {
|
||||
return $http.pendingRequests;
|
||||
}, this.trigger)
|
||||
];
|
||||
|
||||
$body.on(MOUSE_EVENTS, this.trigger);
|
||||
$window.on(WINDOW_EVENTS, this.trigger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply emit reflow, but in a way that can be bound and passed to
|
||||
* other functions. Using _.bind caused extra arguments to be added, and
|
||||
* then emitted to other places. No Bueno
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
ReflowWatcher.prototype._emitReflow = function () {
|
||||
this.emit('reflow');
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit the "reflow" event in the next tick of the digest cycle
|
||||
* @return {void}
|
||||
*/
|
||||
ReflowWatcher.prototype.trigger = function () {
|
||||
$rootScope.$evalAsync(this._emitReflow);
|
||||
};
|
||||
|
||||
/**
|
||||
* Signal to the ReflowWatcher that it should clean up it's listeners
|
||||
* @return {void}
|
||||
*/
|
||||
ReflowWatcher.prototype.destroy = function () {
|
||||
$body.off(MOUSE_EVENTS, this.trigger);
|
||||
$window.off(WINDOW_EVENTS, this.trigger);
|
||||
_.callEach(this._unwatchers);
|
||||
};
|
||||
|
||||
return new ReflowWatcher();
|
||||
};
|
||||
});
|
209
src/kibana/components/vislib/lib/resize_checker.js
Normal file
209
src/kibana/components/vislib/lib/resize_checker.js
Normal file
|
@ -0,0 +1,209 @@
|
|||
define(function (require) {
|
||||
return function ResizeCheckerFactory(Private, Notifier) {
|
||||
var $ = require('jquery');
|
||||
var _ = require('lodash');
|
||||
|
||||
var EventEmitter = Private(require('factories/events'));
|
||||
var reflowWatcher = Private(require('components/reflow_watcher'));
|
||||
var sequencer = require('utils/sequencer');
|
||||
|
||||
var SCHEDULE_LONG = ResizeChecker.SCHEDULE_LONG = sequencer.createEaseOut(
|
||||
250, // shortest delay
|
||||
10000, // longest delay
|
||||
150 // tick count
|
||||
);
|
||||
|
||||
var SCHEDULE_SHORT = ResizeChecker.SCHEDULE_SHORT = sequencer.createEaseIn(
|
||||
5, // shortest delay
|
||||
500, // longest delay
|
||||
100 // tick count
|
||||
);
|
||||
|
||||
// maximum ms that we can delay emitting 'resize'. This is only used
|
||||
// to debounce resizes when the size of the element is constantly changing
|
||||
var MS_MAX_RESIZE_DELAY = ResizeChecker.MS_MAX_RESIZE_DELAY = 500;
|
||||
|
||||
/**
|
||||
* Checks the size of an element on a regular basis. Provides
|
||||
* an event that is emited when the element has changed size.
|
||||
*
|
||||
* @class ResizeChecker
|
||||
* @param {HtmlElement} el - the element to track the size of
|
||||
*/
|
||||
_(ResizeChecker).inherits(EventEmitter);
|
||||
function ResizeChecker(el) {
|
||||
ResizeChecker.Super.call(this);
|
||||
|
||||
this.$el = $(el);
|
||||
this.notify = new Notifier({ location: 'Vislib ResizeChecker ' + _.uniqueId() });
|
||||
|
||||
this.saveSize();
|
||||
|
||||
this.check = _.bind(this.check, this);
|
||||
this.check();
|
||||
|
||||
this.onReflow = _.bind(this.onReflow, this);
|
||||
reflowWatcher.on('reflow', this.onReflow);
|
||||
}
|
||||
|
||||
ResizeChecker.prototype.onReflow = function () {
|
||||
this.startSchedule(SCHEDULE_LONG);
|
||||
};
|
||||
|
||||
/**
|
||||
* Read the size of the element
|
||||
*
|
||||
* @method read
|
||||
* @return {object} - an object with keys `w` (width) and `h` (height)
|
||||
*/
|
||||
ResizeChecker.prototype.read = function () {
|
||||
return {
|
||||
w: this.$el.width(),
|
||||
h: this.$el.height()
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Save the element size, preventing it from being considered as an
|
||||
* update.
|
||||
*
|
||||
* @method save
|
||||
* @param {object} [size] - optional size to save, otherwise #read() is called
|
||||
* @return {boolean} - true if their was a change in the new
|
||||
*/
|
||||
ResizeChecker.prototype.saveSize = function (size) {
|
||||
if (!size) size = this.read();
|
||||
|
||||
if (this._equalsSavedSize(size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._savedSize = size;
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determine if a given size matches the currently saved size.
|
||||
*
|
||||
* @private
|
||||
* @method _equalsSavedSize
|
||||
* @param {object} a - an object that matches the return value of #read()
|
||||
* @return {boolean} - true if the passed in value matches the saved size
|
||||
*/
|
||||
ResizeChecker.prototype._equalsSavedSize = function (a) {
|
||||
var b = this._savedSize || {};
|
||||
return a.w === b.w && a.h === b.h;
|
||||
};
|
||||
|
||||
/**
|
||||
* Read the time that the dirty state last changed.
|
||||
*
|
||||
* @method lastDirtyChange
|
||||
* @return {timestamp} - the unix timestamp (in ms) of the last update
|
||||
* to the dirty state
|
||||
*/
|
||||
ResizeChecker.prototype.lastDirtyChange = function () {
|
||||
return this._dirtyChangeStamp;
|
||||
};
|
||||
|
||||
/**
|
||||
* Record the dirty state
|
||||
*
|
||||
* @method saveDirty
|
||||
* @param {boolean} val
|
||||
* @return {boolean} - true if the dirty state changed by this save
|
||||
*/
|
||||
ResizeChecker.prototype.saveDirty = function (val) {
|
||||
val = !!val;
|
||||
|
||||
if (val === this._isDirty) return false;
|
||||
|
||||
this._isDirty = val;
|
||||
this._dirtyChangeStamp = Date.now();
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* The check routine that executes regularly and will reschedule itself
|
||||
* to run again in the future. It determines the state of the elements
|
||||
* size and decides when to emit the "update" event.
|
||||
*
|
||||
* @method check
|
||||
* @return {void}
|
||||
*/
|
||||
ResizeChecker.prototype.check = function () {
|
||||
var newSize = this.read();
|
||||
var dirty = this.saveSize(newSize);
|
||||
var dirtyChanged = this.saveDirty(dirty);
|
||||
|
||||
var doneDirty = !dirty && dirtyChanged;
|
||||
var muchDirty = dirty && (this.lastDirtyChange() - Date.now() > MS_MAX_RESIZE_DELAY);
|
||||
if (doneDirty || muchDirty) {
|
||||
this.emit('resize', newSize);
|
||||
}
|
||||
|
||||
// if the dirty state is unchanged, continue using the previous schedule
|
||||
if (!dirtyChanged) {
|
||||
return this.continueSchedule();
|
||||
}
|
||||
|
||||
// when the state changes start a new schedule. Use a schedule that quickly
|
||||
// slows down if it is unknown wether there are will be additional changes
|
||||
return this.startSchedule(dirty ? SCHEDULE_SHORT : SCHEDULE_LONG);
|
||||
};
|
||||
|
||||
/**
|
||||
* Start running a new schedule, using one of the SCHEDULE_* constants.
|
||||
*
|
||||
* @method startSchedule
|
||||
* @param {integer[]} schedule - an array of millisecond times that should
|
||||
* be used to schedule calls to #check();
|
||||
* @return {integer} - the id of the next timer
|
||||
*/
|
||||
ResizeChecker.prototype.startSchedule = function (schedule) {
|
||||
this._tick = -1;
|
||||
this._currentSchedule = schedule;
|
||||
return this.continueSchedule();
|
||||
};
|
||||
|
||||
/**
|
||||
* Continue running the current schedule. MUST BE CALLED AFTER #startSchedule()
|
||||
*
|
||||
* @method continueSchedule
|
||||
* @return {integer} - the id of the next timer
|
||||
*/
|
||||
ResizeChecker.prototype.continueSchedule = function () {
|
||||
clearTimeout(this._timerId);
|
||||
|
||||
if (this._tick < this._currentSchedule.length - 1) {
|
||||
// at the end of the schedule, don't progress any further but repeat the last value
|
||||
this._tick += 1;
|
||||
}
|
||||
|
||||
var check = this.check; // already bound
|
||||
var tick = this._tick;
|
||||
var notify = this.notify;
|
||||
var ms = this._currentSchedule[this._tick];
|
||||
return (this._timerId = setTimeout(function () {
|
||||
check();
|
||||
}, ms));
|
||||
};
|
||||
|
||||
/**
|
||||
* Signal that the ResizeChecker should shutdown.
|
||||
*
|
||||
* Cleans up it's listeners and timers.
|
||||
*
|
||||
* @method destroy
|
||||
* @return {void}
|
||||
*/
|
||||
ResizeChecker.prototype.destroy = function () {
|
||||
reflowWatcher.off('reflow', this.check);
|
||||
clearTimeout(this._timerId);
|
||||
};
|
||||
|
||||
return ResizeChecker;
|
||||
};
|
||||
});
|
|
@ -4,6 +4,7 @@ define(function (require) {
|
|||
var _ = require('lodash');
|
||||
|
||||
var Handler = Private(require('components/vislib/lib/handler'));
|
||||
var ResizeChecker = Private(require('components/vislib/lib/resize_checker'));
|
||||
var Events = Private(require('factories/events'));
|
||||
var chartTypes = Private(require('components/vislib/vis_types'));
|
||||
|
||||
|
@ -24,6 +25,12 @@ define(function (require) {
|
|||
this.el = $el.get ? $el.get(0) : $el;
|
||||
this.ChartClass = chartTypes[config.type];
|
||||
this._attr = _.defaults(config || {}, {});
|
||||
|
||||
// bind the resize function so it can be used as an event handler
|
||||
this.resize = _.bind(this.resize, this);
|
||||
|
||||
this.resizeChecker = new ResizeChecker(this.el);
|
||||
this.resizeChecker.on('resize', this.resize);
|
||||
}
|
||||
|
||||
// Exposed API for rendering charts.
|
||||
|
@ -49,24 +56,8 @@ define(function (require) {
|
|||
console.group(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
this.checkSize();
|
||||
};
|
||||
|
||||
// Check for changes to the chart container height and width.
|
||||
Vis.prototype.checkSize = _.debounce(function () {
|
||||
if (arguments.length) { return; }
|
||||
|
||||
// enable auto-resize
|
||||
var size = $(this.el).find('.chart').width() + ':' + $(this.el).find('.chart').height();
|
||||
|
||||
if (this.prevSize !== size) {
|
||||
this.resize();
|
||||
}
|
||||
this.prevSize = size;
|
||||
setTimeout(this.checkSize(), 250);
|
||||
}, 250);
|
||||
|
||||
// Resize the chart
|
||||
Vis.prototype.resize = function () {
|
||||
if (!this.data) {
|
||||
|
@ -78,16 +69,14 @@ define(function (require) {
|
|||
|
||||
// Destroy the chart
|
||||
Vis.prototype.destroy = function () {
|
||||
// Turn off checkSize
|
||||
this.checkSize(false);
|
||||
|
||||
// Removing chart and all elements associated with it
|
||||
d3.select(this.el).selectAll('*').remove();
|
||||
|
||||
// Cleaning up event listeners
|
||||
this.off('click', null);
|
||||
this.off('hover', null);
|
||||
this.off('brush', null);
|
||||
// remove event listeners
|
||||
this.resizeChecker.off('resize', this.resize);
|
||||
|
||||
// pass destroy call down to owned objects
|
||||
this.resizeChecker.destroy();
|
||||
};
|
||||
|
||||
// Set attributes on the chart
|
||||
|
|
|
@ -150,6 +150,15 @@ define(function (require) {
|
|||
// always call flush, it might not do anything
|
||||
flush(this, args);
|
||||
};
|
||||
},
|
||||
chunk: function (arr, count) {
|
||||
var size = Math.ceil(arr.length / count);
|
||||
var chunks = new Array(count);
|
||||
for (var i = 0; i < count; i ++) {
|
||||
var start = i * size;
|
||||
chunks[i] = arr.slice(start, start + size);
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
95
src/kibana/utils/sequencer.js
Normal file
95
src/kibana/utils/sequencer.js
Normal file
|
@ -0,0 +1,95 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
|
||||
function create(min, max, length, mod) {
|
||||
var seq = new Array(length);
|
||||
|
||||
var valueDist = max - min;
|
||||
|
||||
// range of values that the mod creates
|
||||
var modRange = [mod(0, length), mod(length - 1, length)];
|
||||
|
||||
// distance between
|
||||
var modRangeDist = modRange[1] - modRange[0];
|
||||
|
||||
_.times(length, function (i) {
|
||||
var modIPercent = (mod(i, length) - modRange[0]) / modRangeDist;
|
||||
|
||||
// percent applied to distance and added to min to
|
||||
// produce value
|
||||
seq[i] = min + (valueDist * modIPercent);
|
||||
});
|
||||
|
||||
seq.min = min;
|
||||
seq.max = max;
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Create an exponential sequence of numbers.
|
||||
*
|
||||
* Creates a curve resembling:
|
||||
*
|
||||
* ;
|
||||
* /
|
||||
* /
|
||||
* .-'
|
||||
* _.-"
|
||||
* _.-'"
|
||||
* _,.-'"
|
||||
* _,..-'"
|
||||
* _,..-'""
|
||||
* _,..-'""
|
||||
* ____,..--'""
|
||||
*
|
||||
* @param {number} min - the min value to produce
|
||||
* @param {number} max - the max value to produce
|
||||
* @param {number} length - the number of values to produce
|
||||
* @return {number[]} - an array containing the sequence
|
||||
*/
|
||||
createEaseIn: _.partialRight(create, function (i, length) {
|
||||
// generates numbers from 1 to +Infinity
|
||||
return i * Math.pow(i, 1.1111);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Create an sequence of numbers using sine.
|
||||
*
|
||||
* Create a curve resembling:
|
||||
*
|
||||
* ____,..--'""
|
||||
* _,..-'""
|
||||
* _,..-'""
|
||||
* _,..-'"
|
||||
* _,.-'"
|
||||
* _.-'"
|
||||
* _.-"
|
||||
* .-'
|
||||
* /
|
||||
* /
|
||||
* ;
|
||||
*
|
||||
*
|
||||
* @param {number} min - the min value to produce
|
||||
* @param {number} max - the max value to produce
|
||||
* @param {number} length - the number of values to produce
|
||||
* @return {number[]} - an array containing the sequence
|
||||
*/
|
||||
createEaseOut: _.partialRight(create, function (i, length) {
|
||||
// adapted from output of http://www.timotheegroleau.com/Flash/experiments/easing_function_generator.htm
|
||||
// generates numbers from 0 to 100
|
||||
|
||||
var ts = (i /= length) * i;
|
||||
var tc = ts * i;
|
||||
return 100 * (
|
||||
0.5 * tc * ts +
|
||||
-3 * ts * ts +
|
||||
6.5 * tc +
|
||||
-7 * ts +
|
||||
4 * i
|
||||
);
|
||||
})
|
||||
};
|
||||
});
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<title>Kibana4 Tests</title>
|
||||
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
|
||||
<script src="/node_modules/expect.js/expect.js"></script>
|
||||
<script src="/node_modules/expect.js/index.js"></script>
|
||||
<script src="/node_modules/mocha/mocha.js"></script>
|
||||
<script src="/src/bower_components/requirejs/require.js"></script>
|
||||
<script src="/src/kibana/require.config.js"></script>
|
||||
|
@ -79,6 +79,7 @@
|
|||
'specs/utils/interval',
|
||||
'specs/utils/versionmath',
|
||||
'specs/utils/routes/index',
|
||||
'specs/utils/sequencer',
|
||||
'specs/courier/search_source/_get_normalized_sort',
|
||||
'specs/factories/base_object',
|
||||
'specs/state_management/state',
|
||||
|
@ -103,13 +104,15 @@
|
|||
'specs/vislib/handler',
|
||||
'specs/vislib/_error_handler',
|
||||
'specs/vislib/data',
|
||||
'specs/vislib/resize_checker',
|
||||
'specs/utils/diff_time_picker_vals',
|
||||
'specs/factories/events',
|
||||
'specs/index_patterns/_flatten_search_response',
|
||||
'specs/utils/registry/index',
|
||||
'specs/directives/filter_bar',
|
||||
'specs/components/agg_types/index',
|
||||
'specs/components/vis/index'
|
||||
'specs/components/vis/index',
|
||||
'specs/components/reflow_watcher'
|
||||
], function (kibana, sinon) {
|
||||
kibana.load(function () {
|
||||
var xhr = sinon.useFakeXMLHttpRequest();
|
||||
|
|
78
test/unit/specs/components/reflow_watcher.js
Normal file
78
test/unit/specs/components/reflow_watcher.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
define(function (require) {
|
||||
describe('Reflow watcher', function () {
|
||||
require('angular');
|
||||
var $ = require('jquery');
|
||||
var _ = require('lodash');
|
||||
var sinon = require('test_utils/auto_release_sinon');
|
||||
|
||||
var $body = $(document.body);
|
||||
var $window = $(window);
|
||||
var expectStubbedEventAndEl = function (stub, event, $el) {
|
||||
expect(stub.getCalls().some(function (call) {
|
||||
var events = call.args[0].split(' ');
|
||||
return _.contains(events, event) && $el.is(call.thisValue);
|
||||
})).to.be(true);
|
||||
};
|
||||
|
||||
var EventEmitter;
|
||||
var reflowWatcher;
|
||||
var $rootScope;
|
||||
var $onStub;
|
||||
|
||||
beforeEach(module('kibana'));
|
||||
beforeEach(inject(function (Private, $injector) {
|
||||
$rootScope = $injector.get('$rootScope');
|
||||
EventEmitter = Private(require('factories/events'));
|
||||
|
||||
// stub jQuery's $.on method while creating the reflowWatcher
|
||||
$onStub = sinon.stub($.fn, 'on');
|
||||
reflowWatcher = Private(require('components/reflow_watcher'));
|
||||
$onStub.restore();
|
||||
|
||||
// setup the reflowWatchers $http watcher
|
||||
$rootScope.$apply();
|
||||
}));
|
||||
|
||||
it('is an event emitter', function () {
|
||||
expect(reflowWatcher).to.be.an(EventEmitter);
|
||||
});
|
||||
|
||||
describe('listens', function () {
|
||||
it('to "mouseup" on the body', function () {
|
||||
expectStubbedEventAndEl($onStub, 'mouseup', $body);
|
||||
});
|
||||
|
||||
it('to "resize" on the window', function () {
|
||||
expectStubbedEventAndEl($onStub, 'resize', $window);
|
||||
});
|
||||
});
|
||||
|
||||
describe('un-listens in #destroy()', function () {
|
||||
var $offStub;
|
||||
|
||||
beforeEach(function () {
|
||||
$offStub = sinon.stub($.fn, 'off');
|
||||
reflowWatcher.destroy();
|
||||
$offStub.restore();
|
||||
});
|
||||
|
||||
it('to "mouseup" on the body', function () {
|
||||
expectStubbedEventAndEl($offStub, 'mouseup', $body);
|
||||
});
|
||||
|
||||
it('to "resize" on the window', function () {
|
||||
expectStubbedEventAndEl($offStub, 'resize', $window);
|
||||
});
|
||||
});
|
||||
|
||||
it('triggers the "reflow" event within a new angular tick', function () {
|
||||
var stub = sinon.stub();
|
||||
reflowWatcher.on('reflow', stub);
|
||||
reflowWatcher.trigger();
|
||||
|
||||
expect(stub).to.have.property('callCount', 0);
|
||||
$rootScope.$apply();
|
||||
expect(stub).to.have.property('callCount', 1);
|
||||
});
|
||||
});
|
||||
});
|
101
test/unit/specs/utils/sequencer.js
Normal file
101
test/unit/specs/utils/sequencer.js
Normal file
|
@ -0,0 +1,101 @@
|
|||
define(function (require) {
|
||||
describe('sequencer util', function () {
|
||||
var _ = require('lodash');
|
||||
var sequencer = require('utils/sequencer');
|
||||
|
||||
var args = [
|
||||
{ min: 500, max: 7500, length: 1500 },
|
||||
{ min: 50, max: 500, length: 1000 },
|
||||
{ min: 5, max: 50, length: 100 }
|
||||
];
|
||||
|
||||
function eachSeqFor(method, fn) {
|
||||
args.forEach(function (args) {
|
||||
fn(method(args.min, args.max, args.length), args);
|
||||
});
|
||||
}
|
||||
|
||||
function getSlopes(seq, count) {
|
||||
return _.chunk(seq, count).map(function (chunk) {
|
||||
return (_.last(chunk) - _.first(chunk)) / chunk.length;
|
||||
});
|
||||
}
|
||||
|
||||
// using expect() here causes massive GC runs because seq can be +1000 elements
|
||||
function expectedChange(seq, up) {
|
||||
up = !!up;
|
||||
|
||||
if (seq.length < 2) {
|
||||
throw new Error('unable to reach change without at least two elements');
|
||||
}
|
||||
|
||||
seq.forEach(function (n, i) {
|
||||
if (i > 0 && (seq[i - 1] < n) !== up) {
|
||||
throw new Error('expected values to ' + (up ? 'increase': 'decrease'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function generalTests(seq, args) {
|
||||
it('obeys the min arg', function () {
|
||||
expect(Math.min.apply(Math, seq)).to.be(args.min);
|
||||
});
|
||||
|
||||
it('obeys the max arg', function () {
|
||||
expect(Math.max.apply(Math, seq)).to.be(args.max);
|
||||
});
|
||||
|
||||
it('obeys the length arg', function () {
|
||||
expect(seq).to.have.length(args.length);
|
||||
});
|
||||
|
||||
it('always creates increasingly larger values', function () {
|
||||
expectedChange(seq, true);
|
||||
});
|
||||
}
|
||||
|
||||
describe('#createEaseIn', function () {
|
||||
eachSeqFor(sequencer.createEaseIn, function (seq, args) {
|
||||
describe('with args: ' + JSON.stringify(args), function () {
|
||||
generalTests(seq, args);
|
||||
|
||||
it('produces increasing slopes', function () {
|
||||
expectedChange(getSlopes(seq, 2), true);
|
||||
expectedChange(getSlopes(seq, 4), true);
|
||||
expectedChange(getSlopes(seq, 6), true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#createEaseOut', function () {
|
||||
eachSeqFor(sequencer.createEaseOut, function (seq, args) {
|
||||
describe('with args: ' + JSON.stringify(args), function () {
|
||||
generalTests(seq, args);
|
||||
|
||||
it('produces decreasing slopes', function () {
|
||||
expectedChange(getSlopes(seq, 2), false);
|
||||
expectedChange(getSlopes(seq, 4), false);
|
||||
expectedChange(getSlopes(seq, 6), false);
|
||||
});
|
||||
|
||||
// Flipped version of previous test to ensure that expectedChange()
|
||||
// and friends are behaving properly
|
||||
it('doesn\'t produce increasing slopes', function () {
|
||||
expect(function () {
|
||||
expectedChange(getSlopes(seq, 2), true);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
expectedChange(getSlopes(seq, 4), true);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
expectedChange(getSlopes(seq, 6), true);
|
||||
}).to.throwError();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -104,7 +104,7 @@ define(function (require) {
|
|||
addLegend: true
|
||||
};
|
||||
|
||||
vis = new Vis(el, config);
|
||||
vis = new Vis(el[0][0], config);
|
||||
|
||||
handler = new Handler({
|
||||
vis: vis,
|
||||
|
|
199
test/unit/specs/vislib/resize_checker.js
Normal file
199
test/unit/specs/vislib/resize_checker.js
Normal file
|
@ -0,0 +1,199 @@
|
|||
define(function (require) {
|
||||
describe('Vislib Resize Checker', function () {
|
||||
var $ = require('jquery');
|
||||
var _ = require('lodash');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
var sinon = require('test_utils/auto_release_sinon');
|
||||
require('test_utils/no_digest_promises').activateForSuite();
|
||||
|
||||
var ResizeChecker;
|
||||
var EventEmitter;
|
||||
var checker;
|
||||
var reflowWatcher;
|
||||
var spyReflowOn;
|
||||
|
||||
beforeEach(module('kibana'));
|
||||
beforeEach(inject(function (Private) {
|
||||
ResizeChecker = Private(require('components/vislib/lib/resize_checker'));
|
||||
EventEmitter = Private(require('factories/events'));
|
||||
reflowWatcher = Private(require('components/reflow_watcher'));
|
||||
|
||||
spyReflowOn = sinon.spy(reflowWatcher, 'on');
|
||||
checker = new ResizeChecker(
|
||||
$(document.createElement('div'))
|
||||
.appendTo('body')
|
||||
.css('visibility', 'hidden')
|
||||
.get(0)
|
||||
);
|
||||
spyReflowOn.restore();
|
||||
}));
|
||||
|
||||
afterEach(function () {
|
||||
checker.$el.remove();
|
||||
checker.destroy();
|
||||
});
|
||||
|
||||
it('is an event emitter', function () {
|
||||
expect(checker).to.be.a(EventEmitter);
|
||||
});
|
||||
|
||||
it('emits a "resize" event when the el is resized', function (done) {
|
||||
checker.on('resize', function () {
|
||||
done();
|
||||
});
|
||||
|
||||
checker.$el.text('haz contents');
|
||||
checker.check();
|
||||
});
|
||||
|
||||
it('listens for the "reflow" event of the reflowWatchers', function () {
|
||||
expect(spyReflowOn).to.have.property('callCount', 1);
|
||||
var call = spyReflowOn.getCall(0);
|
||||
expect(call.args[0]).to.be('reflow');
|
||||
});
|
||||
|
||||
describe('#read', function () {
|
||||
it('uses jquery to get the width and height of the element', function () {
|
||||
var stubw = sinon.spy($.fn, 'width');
|
||||
var stubh = sinon.spy($.fn, 'height');
|
||||
|
||||
checker.read();
|
||||
|
||||
expect(stubw).to.have.property('callCount', 1);
|
||||
expect(stubw.getCall(0)).to.have.property('thisValue', checker.$el);
|
||||
|
||||
expect(stubh).to.have.property('callCount', 1);
|
||||
expect(stubh.getCall(0)).to.have.property('thisValue', checker.$el);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#saveSize', function () {
|
||||
it('calls #read() when no arg is passed', function () {
|
||||
var stub = sinon.stub(checker, 'read').returns({});
|
||||
|
||||
checker.saveSize();
|
||||
|
||||
expect(stub).to.have.property('callCount', 1);
|
||||
});
|
||||
|
||||
it('saves the size of the element', function () {
|
||||
var football = {};
|
||||
checker.saveSize(football);
|
||||
expect(checker).to.have.property('_savedSize', football);
|
||||
});
|
||||
|
||||
it('returns false if the size matches the previous value', function () {
|
||||
expect(checker.saveSize(checker._savedSize)).to.be(false);
|
||||
});
|
||||
|
||||
it('returns true if the size is different than previous value', function () {
|
||||
expect(checker.saveSize({})).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#check()', function () {
|
||||
var emit;
|
||||
|
||||
beforeEach(function () {
|
||||
emit = sinon.stub(checker, 'emit');
|
||||
|
||||
// prevent the checker from auto-checking
|
||||
checker.destroy();
|
||||
checker.startSchedule = checker.continueSchedule = _.noop;
|
||||
});
|
||||
|
||||
it('does not emit "resize" immediately after a resize, but waits for changes to stop', function () {
|
||||
expect(checker).to.have.property('_isDirty', false);
|
||||
|
||||
checker.$el.css('height', 100);
|
||||
checker.check();
|
||||
|
||||
expect(checker).to.have.property('_isDirty', true);
|
||||
expect(emit).to.have.property('callCount', 0);
|
||||
|
||||
// no change in el size
|
||||
checker.check();
|
||||
|
||||
expect(checker).to.have.property('_isDirty', false);
|
||||
expect(emit).to.have.property('callCount', 1);
|
||||
});
|
||||
|
||||
it('emits "resize" based on MS_MAX_RESIZE_DELAY, even if el\'s constantly changing size', function () {
|
||||
var steps = _.random(5, 10);
|
||||
this.slow(steps * 10);
|
||||
|
||||
// we are going to fake the delay using the fake clock
|
||||
var msStep = Math.floor(ResizeChecker.MS_MAX_RESIZE_DELAY / (steps - 1));
|
||||
var clock = sinon.useFakeTimers();
|
||||
|
||||
_.times(steps, function step(i) {
|
||||
checker.$el.css('height', 100 + i);
|
||||
checker.check();
|
||||
|
||||
expect(checker).to.have.property('_isDirty', true);
|
||||
expect(emit).to.have.property('callCount', i > steps ? 1 : 0);
|
||||
|
||||
clock.tick(msStep); // move the clock forward one step
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('scheduling', function () {
|
||||
var clock;
|
||||
var schedule;
|
||||
|
||||
beforeEach(function () {
|
||||
// prevent the checker from running automatically
|
||||
checker.destroy();
|
||||
clock = sinon.useFakeTimers();
|
||||
|
||||
schedule = [];
|
||||
_.times(25, function () {
|
||||
schedule.push(_.random(3, 250));
|
||||
});
|
||||
});
|
||||
|
||||
it('walks the schedule, using each value as it\'s next timeout', function () {
|
||||
var timerId = checker.startSchedule(schedule);
|
||||
|
||||
// start at 0 even though "start" used the first slot, we will still check it
|
||||
for (var i = 0; i < schedule.length; i++) {
|
||||
expect(clock.timeouts[timerId]).to.have.property('callAt', schedule[i]);
|
||||
timerId = checker.continueSchedule();
|
||||
}
|
||||
});
|
||||
|
||||
it('repeats the last value in the schedule', function () {
|
||||
var timerId = checker.startSchedule(schedule);
|
||||
|
||||
// start at 1, and go until there is one left
|
||||
for (var i = 1; i < schedule.length - 1; i++) {
|
||||
timerId = checker.continueSchedule();
|
||||
}
|
||||
|
||||
var last = _.last(schedule);
|
||||
_.times(5, function () {
|
||||
var timer = clock.timeouts[checker.continueSchedule()];
|
||||
expect(timer).to.have.property('callAt', last);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#destroy()', function () {
|
||||
it('removes the "reflow" event from the reflowWatcher', function () {
|
||||
var stub = sinon.stub(reflowWatcher, 'off');
|
||||
checker.destroy();
|
||||
expect(stub).to.have.property('callCount', 1);
|
||||
expect(stub.calledWith('reflow')).to.be.ok();
|
||||
});
|
||||
|
||||
it('clears the timeout', function () {
|
||||
var spy = sinon.spy(window, 'clearTimeout');
|
||||
checker.destroy();
|
||||
expect(spy).to.have.property('callCount', 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue