mirror of
https://github.com/chartjs/Chart.js.git
synced 2026-03-03 06:54:02 +01:00
Cleanup and upgrade unit tests environment
`karma.conf.ci.js` has been merged into `karma.conf.js` for local testing consistency: `gulp unittestWatch` has been replaced by `gulp unittest --watch` and thus use exactly the same config file. Upgrade to latest jasmine and karma packages and remove deprecated `gulp-karma` dependency (directly use `karma.Server` in gulp). Split `test/mockContext.js` into smaller `test/jasmine.*` modules to make easier unit tests maintenance and finally, move all `*.test.js` files under the `test/specs` folder.
This commit is contained in:
committed by
Evert Timberg
parent
b4dfa38731
commit
c216c0af76
61
gulpfile.js
61
gulpfile.js
@@ -12,12 +12,13 @@ var uglify = require('gulp-uglify');
|
||||
var util = require('gulp-util');
|
||||
var zip = require('gulp-zip');
|
||||
var exec = require('child_process').exec;
|
||||
var karma = require('gulp-karma');
|
||||
var karma = require('karma');
|
||||
var browserify = require('browserify');
|
||||
var source = require('vinyl-source-stream');
|
||||
var merge = require('merge-stream');
|
||||
var collapse = require('bundle-collapser/plugin');
|
||||
var argv = require('yargs').argv
|
||||
var path = require('path');
|
||||
var package = require('./package.json');
|
||||
|
||||
var srcDir = './src/';
|
||||
@@ -34,14 +35,6 @@ var header = "/*!\n" +
|
||||
" * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md\n" +
|
||||
" */\n";
|
||||
|
||||
var preTestFiles = [
|
||||
'./node_modules/moment/min/moment.min.js'
|
||||
];
|
||||
|
||||
var testFiles = [
|
||||
'./test/*.js'
|
||||
];
|
||||
|
||||
gulp.task('bower', bowerTask);
|
||||
gulp.task('build', buildTask);
|
||||
gulp.task('package', packageTask);
|
||||
@@ -53,7 +46,6 @@ gulp.task('size', ['library-size', 'module-sizes']);
|
||||
gulp.task('server', serverTask);
|
||||
gulp.task('validHTML', validHTMLTask);
|
||||
gulp.task('unittest', unittestTask);
|
||||
gulp.task('unittestWatch', unittestWatchTask);
|
||||
gulp.task('library-size', librarySizeTask);
|
||||
gulp.task('module-sizes', moduleSizesTask);
|
||||
gulp.task('_open', _openTask);
|
||||
@@ -157,6 +149,7 @@ function lintTask() {
|
||||
'beforeEach',
|
||||
'describe',
|
||||
'expect',
|
||||
'fail',
|
||||
'it',
|
||||
'jasmine',
|
||||
'moment',
|
||||
@@ -177,37 +170,31 @@ function validHTMLTask() {
|
||||
}
|
||||
|
||||
function startTest() {
|
||||
return [].concat(preTestFiles).concat([
|
||||
'./src/**/*.js',
|
||||
'./test/mockContext.js'
|
||||
]).concat(
|
||||
argv.inputs?
|
||||
argv.inputs.split(';'):
|
||||
testFiles);
|
||||
return [
|
||||
'./node_modules/moment/min/moment.min.js',
|
||||
'./test/jasmine.index.js',
|
||||
'./src/**/*.js',
|
||||
].concat(
|
||||
argv.inputs?
|
||||
argv.inputs.split(';'):
|
||||
['./test/specs/**/*.js']
|
||||
);
|
||||
}
|
||||
|
||||
function unittestTask() {
|
||||
return gulp.src(startTest())
|
||||
.pipe(karma({
|
||||
configFile: 'karma.conf.ci.js',
|
||||
action: 'run'
|
||||
}));
|
||||
function unittestTask(done) {
|
||||
new karma.Server({
|
||||
configFile: path.join(__dirname, 'karma.conf.js'),
|
||||
singleRun: !argv.watch,
|
||||
files: startTest(),
|
||||
}, done).start();
|
||||
}
|
||||
|
||||
function unittestWatchTask() {
|
||||
return gulp.src(startTest())
|
||||
.pipe(karma({
|
||||
configFile: 'karma.conf.js',
|
||||
action: 'watch'
|
||||
}));
|
||||
}
|
||||
|
||||
function coverageTask() {
|
||||
return gulp.src(startTest())
|
||||
.pipe(karma({
|
||||
configFile: 'karma.coverage.conf.js',
|
||||
action: 'run'
|
||||
}));
|
||||
function coverageTask(done) {
|
||||
new karma.Server({
|
||||
configFile: path.join(__dirname, 'karma.coverage.conf.js'),
|
||||
files: startTest(),
|
||||
singleRun: true,
|
||||
}, done).start();
|
||||
}
|
||||
|
||||
function librarySizeTask() {
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
module.exports = function(config) {
|
||||
var configuration = {
|
||||
browsers: ['Firefox'],
|
||||
customLaunchers: {
|
||||
Chrome_travis_ci: {
|
||||
base: 'Chrome',
|
||||
flags: ['--no-sandbox']
|
||||
}
|
||||
},
|
||||
frameworks: ['browserify', 'jasmine'],
|
||||
reporters: ['progress', 'html'],
|
||||
preprocessors: {
|
||||
'src/**/*.js': ['browserify']
|
||||
},
|
||||
browserify: {
|
||||
debug: true
|
||||
}
|
||||
};
|
||||
|
||||
if (process.env.TRAVIS) {
|
||||
configuration.browsers.push('Chrome_travis_ci');
|
||||
}
|
||||
|
||||
config.set(configuration);
|
||||
};
|
||||
@@ -1,14 +1,33 @@
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
browsers: ['Chrome', 'Firefox'],
|
||||
/* eslint camelcase: 0 */
|
||||
|
||||
module.exports = function(karma) {
|
||||
var config = {
|
||||
browsers: ['Firefox'],
|
||||
frameworks: ['browserify', 'jasmine'],
|
||||
reporters: ['progress', 'html'],
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
|
||||
preprocessors: {
|
||||
'src/**/*.js': ['browserify']
|
||||
'./test/jasmine.index.js': ['browserify'],
|
||||
'./src/**/*.js': ['browserify']
|
||||
},
|
||||
|
||||
browserify: {
|
||||
debug: true
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
// https://swizec.com/blog/how-to-run-javascript-tests-in-chrome-on-travis/swizec/6647
|
||||
if (process.env.TRAVIS) {
|
||||
config.browsers.push('chrome_travis_ci');
|
||||
config.customLaunchers = {
|
||||
chrome_travis_ci: {
|
||||
base: 'Chrome',
|
||||
flags: ['--no-sandbox']
|
||||
}
|
||||
};
|
||||
} else {
|
||||
config.browsers.push('Chrome');
|
||||
}
|
||||
|
||||
karma.set(config);
|
||||
};
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
module.exports = function(config) {
|
||||
var configuration = {
|
||||
browsers: ['Firefox'],
|
||||
|
||||
frameworks: ['browserify', 'jasmine'],
|
||||
reporters: ['progress', 'coverage'],
|
||||
|
||||
preprocessors: {
|
||||
'src/**/*.js': ['browserify']
|
||||
'./test/jasmine.index.js': ['browserify'],
|
||||
'./src/**/*.js': ['browserify']
|
||||
},
|
||||
|
||||
browserify: {
|
||||
debug: true,
|
||||
transform: [['browserify-istanbul', {
|
||||
@@ -15,8 +17,7 @@ module.exports = function(config) {
|
||||
}
|
||||
}]]
|
||||
},
|
||||
|
||||
reporters: ['progress', 'coverage'],
|
||||
|
||||
coverageReporter: {
|
||||
dir: 'coverage/',
|
||||
reporters: [
|
||||
|
||||
19
package.json
19
package.json
@@ -21,22 +21,21 @@
|
||||
"gulp-file": "^0.3.0",
|
||||
"gulp-html-validator": "^0.0.2",
|
||||
"gulp-insert": "~0.5.0",
|
||||
"gulp-karma": "0.0.4",
|
||||
"gulp-replace": "^0.5.4",
|
||||
"gulp-size": "~0.4.0",
|
||||
"gulp-streamify": "^1.0.2",
|
||||
"gulp-uglify": "~2.0.x",
|
||||
"gulp-util": "~2.2.x",
|
||||
"gulp-zip": "~3.2.0",
|
||||
"jasmine": "^2.3.2",
|
||||
"jasmine-core": "^2.3.4",
|
||||
"karma": "^0.12.37",
|
||||
"karma-browserify": "^5.0.1",
|
||||
"karma-chrome-launcher": "^0.2.0",
|
||||
"karma-coverage": "^0.5.1",
|
||||
"karma-firefox-launcher": "^0.1.6",
|
||||
"karma-jasmine": "^0.3.6",
|
||||
"karma-jasmine-html-reporter": "^0.1.8",
|
||||
"jasmine": "^2.5.0",
|
||||
"jasmine-core": "^2.5.0",
|
||||
"karma": "^1.5.0",
|
||||
"karma-browserify": "^5.1.0",
|
||||
"karma-chrome-launcher": "^2.0.0",
|
||||
"karma-coverage": "^1.1.0",
|
||||
"karma-firefox-launcher": "^1.0.0",
|
||||
"karma-jasmine": "^1.1.0",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"merge-stream": "^1.0.0",
|
||||
"vinyl-source-stream": "^1.1.0",
|
||||
"watchify": "^3.7.0",
|
||||
|
||||
125
test/jasmine.context.js
Normal file
125
test/jasmine.context.js
Normal file
@@ -0,0 +1,125 @@
|
||||
// Code from http://stackoverflow.com/questions/4406864/html-canvas-unit-testing
|
||||
var Context = function() {
|
||||
this._calls = []; // names/args of recorded calls
|
||||
this._initMethods();
|
||||
|
||||
this._fillStyle = null;
|
||||
this._lineCap = null;
|
||||
this._lineDashOffset = null;
|
||||
this._lineJoin = null;
|
||||
this._lineWidth = null;
|
||||
this._strokeStyle = null;
|
||||
|
||||
// Define properties here so that we can record each time they are set
|
||||
Object.defineProperties(this, {
|
||||
fillStyle: {
|
||||
get: function() {
|
||||
return this._fillStyle;
|
||||
},
|
||||
set: function(style) {
|
||||
this._fillStyle = style;
|
||||
this.record('setFillStyle', [style]);
|
||||
}
|
||||
},
|
||||
lineCap: {
|
||||
get: function() {
|
||||
return this._lineCap;
|
||||
},
|
||||
set: function(cap) {
|
||||
this._lineCap = cap;
|
||||
this.record('setLineCap', [cap]);
|
||||
}
|
||||
},
|
||||
lineDashOffset: {
|
||||
get: function() {
|
||||
return this._lineDashOffset;
|
||||
},
|
||||
set: function(offset) {
|
||||
this._lineDashOffset = offset;
|
||||
this.record('setLineDashOffset', [offset]);
|
||||
}
|
||||
},
|
||||
lineJoin: {
|
||||
get: function() {
|
||||
return this._lineJoin;
|
||||
},
|
||||
set: function(join) {
|
||||
this._lineJoin = join;
|
||||
this.record('setLineJoin', [join]);
|
||||
}
|
||||
},
|
||||
lineWidth: {
|
||||
get: function() {
|
||||
return this._lineWidth;
|
||||
},
|
||||
set: function(width) {
|
||||
this._lineWidth = width;
|
||||
this.record('setLineWidth', [width]);
|
||||
}
|
||||
},
|
||||
strokeStyle: {
|
||||
get: function() {
|
||||
return this._strokeStyle;
|
||||
},
|
||||
set: function(style) {
|
||||
this._strokeStyle = style;
|
||||
this.record('setStrokeStyle', [style]);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Context.prototype._initMethods = function() {
|
||||
// define methods to test here
|
||||
// no way to introspect so we have to do some extra work :(
|
||||
var me = this;
|
||||
var methods = {
|
||||
arc: function() {},
|
||||
beginPath: function() {},
|
||||
bezierCurveTo: function() {},
|
||||
clearRect: function() {},
|
||||
closePath: function() {},
|
||||
fill: function() {},
|
||||
fillRect: function() {},
|
||||
fillText: function() {},
|
||||
lineTo: function() {},
|
||||
measureText: function(text) {
|
||||
// return the number of characters * fixed size
|
||||
return text ? {width: text.length * 10} : {width: 0};
|
||||
},
|
||||
moveTo: function() {},
|
||||
quadraticCurveTo: function() {},
|
||||
restore: function() {},
|
||||
rotate: function() {},
|
||||
save: function() {},
|
||||
setLineDash: function() {},
|
||||
stroke: function() {},
|
||||
strokeRect: function() {},
|
||||
setTransform: function() {},
|
||||
translate: function() {},
|
||||
};
|
||||
|
||||
Object.keys(methods).forEach(function(name) {
|
||||
me[name] = function() {
|
||||
me.record(name, arguments);
|
||||
return methods[name].apply(me, arguments);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
Context.prototype.record = function(methodName, args) {
|
||||
this._calls.push({
|
||||
name: methodName,
|
||||
args: Array.prototype.slice.call(args)
|
||||
});
|
||||
};
|
||||
|
||||
Context.prototype.getCalls = function() {
|
||||
return this._calls;
|
||||
};
|
||||
|
||||
Context.prototype.resetCalls = function() {
|
||||
this._calls = [];
|
||||
};
|
||||
|
||||
module.exports = Context;
|
||||
53
test/jasmine.index.js
Normal file
53
test/jasmine.index.js
Normal file
@@ -0,0 +1,53 @@
|
||||
var Context = require('./jasmine.context');
|
||||
var matchers = require('./jasmine.matchers');
|
||||
var utils = require('./jasmine.utils');
|
||||
|
||||
(function() {
|
||||
|
||||
// Keep track of all acquired charts to automatically release them after each specs
|
||||
var charts = {};
|
||||
|
||||
function acquireChart() {
|
||||
var chart = utils.acquireChart.apply(utils, arguments);
|
||||
charts[chart.id] = chart;
|
||||
return chart;
|
||||
}
|
||||
|
||||
function releaseChart(chart) {
|
||||
utils.releaseChart.apply(utils, arguments);
|
||||
delete charts[chart.id];
|
||||
}
|
||||
|
||||
function createMockContext() {
|
||||
return new Context();
|
||||
}
|
||||
|
||||
window.acquireChart = acquireChart;
|
||||
window.releaseChart = releaseChart;
|
||||
window.createMockContext = createMockContext;
|
||||
|
||||
// some style initialization to limit differences between browsers across different plateforms.
|
||||
utils.injectCSS(
|
||||
'.chartjs-wrapper, .chartjs-wrapper canvas {' +
|
||||
'border: 0;' +
|
||||
'margin: 0;' +
|
||||
'padding: 0;' +
|
||||
'}' +
|
||||
'.chartjs-wrapper {' +
|
||||
'position: absolute' +
|
||||
'}');
|
||||
|
||||
beforeEach(function() {
|
||||
jasmine.addMatchers(matchers);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
// Auto releasing acquired charts
|
||||
Object.keys(charts).forEach(function(id) {
|
||||
var chart = charts[id];
|
||||
if (!(chart.$test || {}).persistent) {
|
||||
releaseChart(chart);
|
||||
}
|
||||
});
|
||||
});
|
||||
}());
|
||||
113
test/jasmine.matchers.js
Normal file
113
test/jasmine.matchers.js
Normal file
@@ -0,0 +1,113 @@
|
||||
'use strict';
|
||||
|
||||
function toBeCloseToPixel() {
|
||||
return {
|
||||
compare: function(actual, expected) {
|
||||
var result = false;
|
||||
|
||||
if (!isNaN(actual) && !isNaN(expected)) {
|
||||
var diff = Math.abs(actual - expected);
|
||||
var A = Math.abs(actual);
|
||||
var B = Math.abs(expected);
|
||||
var percentDiff = 0.005; // 0.5% diff
|
||||
result = (diff <= (A > B ? A : B) * percentDiff) || diff < 2; // 2 pixels is fine
|
||||
}
|
||||
|
||||
return {pass: result};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function toEqualOneOf() {
|
||||
return {
|
||||
compare: function(actual, expecteds) {
|
||||
var result = false;
|
||||
for (var i = 0, l = expecteds.length; i < l; i++) {
|
||||
if (actual === expecteds[i]) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {
|
||||
pass: result
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function toBeValidChart() {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
var message = null;
|
||||
|
||||
if (!(actual instanceof Chart)) {
|
||||
message = 'Expected ' + actual + ' to be an instance of Chart';
|
||||
} else if (!(actual.canvas instanceof HTMLCanvasElement)) {
|
||||
message = 'Expected canvas to be an instance of HTMLCanvasElement';
|
||||
} else if (!(actual.ctx instanceof CanvasRenderingContext2D)) {
|
||||
message = 'Expected context to be an instance of CanvasRenderingContext2D';
|
||||
} else if (typeof actual.height !== 'number' || !isFinite(actual.height)) {
|
||||
message = 'Expected height to be a strict finite number';
|
||||
} else if (typeof actual.width !== 'number' || !isFinite(actual.width)) {
|
||||
message = 'Expected width to be a strict finite number';
|
||||
}
|
||||
|
||||
return {
|
||||
message: message? message : 'Expected ' + actual + ' to be valid chart',
|
||||
pass: !message
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function toBeChartOfSize() {
|
||||
return {
|
||||
compare: function(actual, expected) {
|
||||
var res = toBeValidChart().compare(actual);
|
||||
if (!res.pass) {
|
||||
return res;
|
||||
}
|
||||
|
||||
var message = null;
|
||||
var canvas = actual.ctx.canvas;
|
||||
var style = getComputedStyle(canvas);
|
||||
var pixelRatio = window.devicePixelRatio;
|
||||
var dh = parseInt(style.height, 10);
|
||||
var dw = parseInt(style.width, 10);
|
||||
var rh = canvas.height;
|
||||
var rw = canvas.width;
|
||||
var orh = rh / pixelRatio;
|
||||
var orw = rw / pixelRatio;
|
||||
|
||||
// sanity checks
|
||||
if (actual.height !== orh) {
|
||||
message = 'Expected chart height ' + actual.height + ' to be equal to original render height ' + orh;
|
||||
} else if (actual.width !== orw) {
|
||||
message = 'Expected chart width ' + actual.width + ' to be equal to original render width ' + orw;
|
||||
}
|
||||
|
||||
// validity checks
|
||||
if (dh !== expected.dh) {
|
||||
message = 'Expected display height ' + dh + ' to be equal to ' + expected.dh;
|
||||
} else if (dw !== expected.dw) {
|
||||
message = 'Expected display width ' + dw + ' to be equal to ' + expected.dw;
|
||||
} else if (rh !== expected.rh) {
|
||||
message = 'Expected render height ' + rh + ' to be equal to ' + expected.rh;
|
||||
} else if (rw !== expected.rw) {
|
||||
message = 'Expected render width ' + rw + ' to be equal to ' + expected.rw;
|
||||
}
|
||||
|
||||
return {
|
||||
message: message? message : 'Expected ' + actual + ' to be a chart of size ' + expected,
|
||||
pass: !message
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
toBeCloseToPixel: toBeCloseToPixel,
|
||||
toEqualOneOf: toEqualOneOf,
|
||||
toBeValidChart: toBeValidChart,
|
||||
toBeChartOfSize: toBeChartOfSize
|
||||
};
|
||||
76
test/jasmine.utils.js
Normal file
76
test/jasmine.utils.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Injects a new canvas (and div wrapper) and creates teh associated Chart instance
|
||||
* using the given config. Additional options allow tweaking elements generation.
|
||||
* @param {object} config - Chart config.
|
||||
* @param {object} options - Chart acquisition options.
|
||||
* @param {object} options.canvas - Canvas attributes.
|
||||
* @param {object} options.wrapper - Canvas wrapper attributes.
|
||||
* @param {boolean} options.persistent - If true, the chart will not be released after the spec.
|
||||
*/
|
||||
function acquireChart(config, options) {
|
||||
var wrapper = document.createElement('div');
|
||||
var canvas = document.createElement('canvas');
|
||||
var chart, key;
|
||||
|
||||
config = config || {};
|
||||
options = options || {};
|
||||
options.canvas = options.canvas || {height: 512, width: 512};
|
||||
options.wrapper = options.wrapper || {class: 'chartjs-wrapper'};
|
||||
|
||||
for (key in options.canvas) {
|
||||
if (options.canvas.hasOwnProperty(key)) {
|
||||
canvas.setAttribute(key, options.canvas[key]);
|
||||
}
|
||||
}
|
||||
|
||||
for (key in options.wrapper) {
|
||||
if (options.wrapper.hasOwnProperty(key)) {
|
||||
wrapper.setAttribute(key, options.wrapper[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// by default, remove chart animation and auto resize
|
||||
config.options = config.options || {};
|
||||
config.options.animation = config.options.animation === undefined? false : config.options.animation;
|
||||
config.options.responsive = config.options.responsive === undefined? false : config.options.responsive;
|
||||
config.options.defaultFontFamily = config.options.defaultFontFamily || 'Arial';
|
||||
|
||||
wrapper.appendChild(canvas);
|
||||
window.document.body.appendChild(wrapper);
|
||||
|
||||
chart = new Chart(canvas.getContext('2d'), config);
|
||||
chart.$test = {
|
||||
persistent: options.persistent,
|
||||
wrapper: wrapper
|
||||
};
|
||||
|
||||
return chart;
|
||||
}
|
||||
|
||||
function releaseChart(chart) {
|
||||
chart.destroy();
|
||||
|
||||
var wrapper = (chart.$test || {}).wrapper;
|
||||
if (wrapper && wrapper.parentNode) {
|
||||
wrapper.parentNode.removeChild(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
function injectCSS(css) {
|
||||
// http://stackoverflow.com/q/3922139
|
||||
var head = document.getElementsByTagName('head')[0];
|
||||
var style = document.createElement('style');
|
||||
style.setAttribute('type', 'text/css');
|
||||
if (style.styleSheet) { // IE
|
||||
style.styleSheet.cssText = css;
|
||||
} else {
|
||||
style.appendChild(document.createTextNode(css));
|
||||
}
|
||||
head.appendChild(style);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
injectCSS: injectCSS,
|
||||
acquireChart: acquireChart,
|
||||
releaseChart: releaseChart
|
||||
};
|
||||
@@ -1,348 +0,0 @@
|
||||
/* eslint guard-for-in: 1 */
|
||||
/* eslint camelcase: 1 */
|
||||
(function() {
|
||||
// Code from http://stackoverflow.com/questions/4406864/html-canvas-unit-testing
|
||||
var Context = function() {
|
||||
this._calls = []; // names/args of recorded calls
|
||||
this._initMethods();
|
||||
|
||||
this._fillStyle = null;
|
||||
this._lineCap = null;
|
||||
this._lineDashOffset = null;
|
||||
this._lineJoin = null;
|
||||
this._lineWidth = null;
|
||||
this._strokeStyle = null;
|
||||
|
||||
// Define properties here so that we can record each time they are set
|
||||
Object.defineProperties(this, {
|
||||
fillStyle: {
|
||||
get: function() {
|
||||
return this._fillStyle;
|
||||
},
|
||||
set: function(style) {
|
||||
this._fillStyle = style;
|
||||
this.record('setFillStyle', [style]);
|
||||
}
|
||||
},
|
||||
lineCap: {
|
||||
get: function() {
|
||||
return this._lineCap;
|
||||
},
|
||||
set: function(cap) {
|
||||
this._lineCap = cap;
|
||||
this.record('setLineCap', [cap]);
|
||||
}
|
||||
},
|
||||
lineDashOffset: {
|
||||
get: function() {
|
||||
return this._lineDashOffset;
|
||||
},
|
||||
set: function(offset) {
|
||||
this._lineDashOffset = offset;
|
||||
this.record('setLineDashOffset', [offset]);
|
||||
}
|
||||
},
|
||||
lineJoin: {
|
||||
get: function() {
|
||||
return this._lineJoin;
|
||||
},
|
||||
set: function(join) {
|
||||
this._lineJoin = join;
|
||||
this.record('setLineJoin', [join]);
|
||||
}
|
||||
},
|
||||
lineWidth: {
|
||||
get: function() {
|
||||
return this._lineWidth;
|
||||
},
|
||||
set: function(width) {
|
||||
this._lineWidth = width;
|
||||
this.record('setLineWidth', [width]);
|
||||
}
|
||||
},
|
||||
strokeStyle: {
|
||||
get: function() {
|
||||
return this._strokeStyle;
|
||||
},
|
||||
set: function(style) {
|
||||
this._strokeStyle = style;
|
||||
this.record('setStrokeStyle', [style]);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Context.prototype._initMethods = function() {
|
||||
// define methods to test here
|
||||
// no way to introspect so we have to do some extra work :(
|
||||
var methods = {
|
||||
arc: function() {},
|
||||
beginPath: function() {},
|
||||
bezierCurveTo: function() {},
|
||||
clearRect: function() {},
|
||||
closePath: function() {},
|
||||
fill: function() {},
|
||||
fillRect: function() {},
|
||||
fillText: function() {},
|
||||
lineTo: function() {},
|
||||
measureText: function(text) {
|
||||
// return the number of characters * fixed size
|
||||
return text ? {width: text.length * 10} : {width: 0};
|
||||
},
|
||||
moveTo: function() {},
|
||||
quadraticCurveTo: function() {},
|
||||
restore: function() {},
|
||||
rotate: function() {},
|
||||
save: function() {},
|
||||
setLineDash: function() {},
|
||||
stroke: function() {},
|
||||
strokeRect: function() {},
|
||||
setTransform: function() {},
|
||||
translate: function() {},
|
||||
};
|
||||
|
||||
// attach methods to the class itself
|
||||
var me = this;
|
||||
var methodName;
|
||||
|
||||
var addMethod = function(name, method) {
|
||||
me[methodName] = function() {
|
||||
me.record(name, arguments);
|
||||
return method.apply(me, arguments);
|
||||
};
|
||||
};
|
||||
|
||||
for (methodName in methods) {
|
||||
var method = methods[methodName];
|
||||
|
||||
addMethod(methodName, method);
|
||||
}
|
||||
};
|
||||
|
||||
Context.prototype.record = function(methodName, args) {
|
||||
this._calls.push({
|
||||
name: methodName,
|
||||
args: Array.prototype.slice.call(args)
|
||||
});
|
||||
};
|
||||
|
||||
Context.prototype.getCalls = function() {
|
||||
return this._calls;
|
||||
};
|
||||
|
||||
Context.prototype.resetCalls = function() {
|
||||
this._calls = [];
|
||||
};
|
||||
|
||||
window.createMockContext = function() {
|
||||
return new Context();
|
||||
};
|
||||
|
||||
// Custom matcher
|
||||
function toBeCloseToPixel() {
|
||||
return {
|
||||
compare: function(actual, expected) {
|
||||
var result = false;
|
||||
|
||||
if (!isNaN(actual) && !isNaN(expected)) {
|
||||
var diff = Math.abs(actual - expected);
|
||||
var A = Math.abs(actual);
|
||||
var B = Math.abs(expected);
|
||||
var percentDiff = 0.005; // 0.5% diff
|
||||
result = (diff <= (A > B ? A : B) * percentDiff) || diff < 2; // 2 pixels is fine
|
||||
}
|
||||
|
||||
return {pass: result};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function toEqualOneOf() {
|
||||
return {
|
||||
compare: function(actual, expecteds) {
|
||||
var result = false;
|
||||
for (var i = 0, l = expecteds.length; i < l; i++) {
|
||||
if (actual === expecteds[i]) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {
|
||||
pass: result
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function toBeValidChart() {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
var message = null;
|
||||
|
||||
if (!(actual instanceof Chart)) {
|
||||
message = 'Expected ' + actual + ' to be an instance of Chart';
|
||||
} else if (!(actual.canvas instanceof HTMLCanvasElement)) {
|
||||
message = 'Expected canvas to be an instance of HTMLCanvasElement';
|
||||
} else if (!(actual.ctx instanceof CanvasRenderingContext2D)) {
|
||||
message = 'Expected context to be an instance of CanvasRenderingContext2D';
|
||||
} else if (typeof actual.height !== 'number' || !isFinite(actual.height)) {
|
||||
message = 'Expected height to be a strict finite number';
|
||||
} else if (typeof actual.width !== 'number' || !isFinite(actual.width)) {
|
||||
message = 'Expected width to be a strict finite number';
|
||||
}
|
||||
|
||||
return {
|
||||
message: message? message : 'Expected ' + actual + ' to be valid chart',
|
||||
pass: !message
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function toBeChartOfSize() {
|
||||
return {
|
||||
compare: function(actual, expected) {
|
||||
var res = toBeValidChart().compare(actual);
|
||||
if (!res.pass) {
|
||||
return res;
|
||||
}
|
||||
|
||||
var message = null;
|
||||
var canvas = actual.ctx.canvas;
|
||||
var style = getComputedStyle(canvas);
|
||||
var pixelRatio = window.devicePixelRatio;
|
||||
var dh = parseInt(style.height, 10);
|
||||
var dw = parseInt(style.width, 10);
|
||||
var rh = canvas.height;
|
||||
var rw = canvas.width;
|
||||
var orh = rh / pixelRatio;
|
||||
var orw = rw / pixelRatio;
|
||||
|
||||
// sanity checks
|
||||
if (actual.height !== orh) {
|
||||
message = 'Expected chart height ' + actual.height + ' to be equal to original render height ' + orh;
|
||||
} else if (actual.width !== orw) {
|
||||
message = 'Expected chart width ' + actual.width + ' to be equal to original render width ' + orw;
|
||||
}
|
||||
|
||||
// validity checks
|
||||
if (dh !== expected.dh) {
|
||||
message = 'Expected display height ' + dh + ' to be equal to ' + expected.dh;
|
||||
} else if (dw !== expected.dw) {
|
||||
message = 'Expected display width ' + dw + ' to be equal to ' + expected.dw;
|
||||
} else if (rh !== expected.rh) {
|
||||
message = 'Expected render height ' + rh + ' to be equal to ' + expected.rh;
|
||||
} else if (rw !== expected.rw) {
|
||||
message = 'Expected render width ' + rw + ' to be equal to ' + expected.rw;
|
||||
}
|
||||
|
||||
return {
|
||||
message: message? message : 'Expected ' + actual + ' to be a chart of size ' + expected,
|
||||
pass: !message
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
jasmine.addMatchers({
|
||||
toBeCloseToPixel: toBeCloseToPixel,
|
||||
toEqualOneOf: toEqualOneOf,
|
||||
toBeValidChart: toBeValidChart,
|
||||
toBeChartOfSize: toBeChartOfSize
|
||||
});
|
||||
});
|
||||
|
||||
// Canvas injection helpers
|
||||
var charts = {};
|
||||
|
||||
/**
|
||||
* Injects a new canvas (and div wrapper) and creates teh associated Chart instance
|
||||
* using the given config. Additional options allow tweaking elements generation.
|
||||
* @param {object} config - Chart config.
|
||||
* @param {object} options - Chart acquisition options.
|
||||
* @param {object} options.canvas - Canvas attributes.
|
||||
* @param {object} options.wrapper - Canvas wrapper attributes.
|
||||
* @param {boolean} options.persistent - If true, the chart will not be released after the spec.
|
||||
*/
|
||||
function acquireChart(config, options) {
|
||||
var wrapper = document.createElement('div');
|
||||
var canvas = document.createElement('canvas');
|
||||
var chart, key;
|
||||
|
||||
config = config || {};
|
||||
options = options || {};
|
||||
options.canvas = options.canvas || {height: 512, width: 512};
|
||||
options.wrapper = options.wrapper || {class: 'chartjs-wrapper'};
|
||||
|
||||
for (key in options.canvas) {
|
||||
if (options.canvas.hasOwnProperty(key)) {
|
||||
canvas.setAttribute(key, options.canvas[key]);
|
||||
}
|
||||
}
|
||||
|
||||
for (key in options.wrapper) {
|
||||
if (options.wrapper.hasOwnProperty(key)) {
|
||||
wrapper.setAttribute(key, options.wrapper[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// by default, remove chart animation and auto resize
|
||||
config.options = config.options || {};
|
||||
config.options.animation = config.options.animation === undefined? false : config.options.animation;
|
||||
config.options.responsive = config.options.responsive === undefined? false : config.options.responsive;
|
||||
config.options.defaultFontFamily = config.options.defaultFontFamily || 'Arial';
|
||||
|
||||
wrapper.appendChild(canvas);
|
||||
window.document.body.appendChild(wrapper);
|
||||
|
||||
chart = new Chart(canvas.getContext('2d'), config);
|
||||
chart._test_persistent = options.persistent;
|
||||
chart._test_wrapper = wrapper;
|
||||
charts[chart.id] = chart;
|
||||
return chart;
|
||||
}
|
||||
|
||||
function releaseChart(chart) {
|
||||
chart.destroy();
|
||||
chart._test_wrapper.remove();
|
||||
delete charts[chart.id];
|
||||
}
|
||||
|
||||
afterEach(function() {
|
||||
// Auto releasing acquired charts
|
||||
for (var id in charts) {
|
||||
var chart = charts[id];
|
||||
if (!chart._test_persistent) {
|
||||
releaseChart(chart);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function injectCSS(css) {
|
||||
// http://stackoverflow.com/q/3922139
|
||||
var head = document.getElementsByTagName('head')[0];
|
||||
var style = document.createElement('style');
|
||||
style.setAttribute('type', 'text/css');
|
||||
if (style.styleSheet) { // IE
|
||||
style.styleSheet.cssText = css;
|
||||
} else {
|
||||
style.appendChild(document.createTextNode(css));
|
||||
}
|
||||
head.appendChild(style);
|
||||
}
|
||||
|
||||
window.acquireChart = acquireChart;
|
||||
window.releaseChart = releaseChart;
|
||||
|
||||
// some style initialization to limit differences between browsers across different plateforms.
|
||||
injectCSS(
|
||||
'.chartjs-wrapper, .chartjs-wrapper canvas {' +
|
||||
'border: 0;' +
|
||||
'margin: 0;' +
|
||||
'padding: 0;' +
|
||||
'}' +
|
||||
'.chartjs-wrapper {' +
|
||||
'position: absolute' +
|
||||
'}');
|
||||
}());
|
||||
Reference in New Issue
Block a user