From: Simon Brunel Date: Sun, 5 Mar 2017 16:49:12 +0000 (+0100) Subject: Introduce unit test based on image comparison (#3988) X-Git-Tag: v2.6.0~2^2~46 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=1ca0ffb5d5b6c2072176fd36fa85a58c483aa434;p=thirdparty%2FChart.js.git Introduce unit test based on image comparison (#3988) Attempt to make easier the creation of unit tests that check the drawing output. Until now, this was done by checking calls on a 'fake' context, which is hard to maintain (need to update pixel values by hands) and also not reliable when optimizing code (i.e. different calls sequence but same result). As of now, it's possible to define 'auto' tests based on JSON/PNG fixtures: chart is generated from the JSON file and compared to the associated PNG image. The image diff is done using `pixelmatch`. As an example (and in preparation of the `filler` plugin), add auto tests for the line element `fill` options. --- diff --git a/gulpfile.js b/gulpfile.js index 868001696..c9d604713 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -171,6 +171,8 @@ function validHTMLTask() { function startTest() { return [ + {pattern: './test/fixtures/**/*.json', included: false}, + {pattern: './test/fixtures/**/*.png', included: false}, './node_modules/moment/min/moment.min.js', './test/jasmine.index.js', './src/**/*.js', diff --git a/package.json b/package.json index 374f90d17..24a313f4b 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "karma-jasmine": "^1.1.0", "karma-jasmine-html-reporter": "^0.2.2", "merge-stream": "^1.0.0", + "pixelmatch": "^4.0.2", "vinyl-source-stream": "^1.1.0", "watchify": "^3.7.0", "yargs": "^5.0.0" diff --git a/test/fixtures/element.line/fill-line-bottom-span.json b/test/fixtures/element.line/fill-line-bottom-span.json new file mode 100644 index 000000000..ec6bb4b0d --- /dev/null +++ b/test/fixtures/element.line/fill-line-bottom-span.json @@ -0,0 +1,55 @@ +{ + "config": { + "type": "line", + "data": { + "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"], + "datasets": [{ + "backgroundColor": "rgba(0, 0, 255, 0.25)", + "data": [null, null, 2, 3, 4, -4, -2, 1, 0] + }, { + "backgroundColor": "rgba(0, 255, 0, 0.25)", + "data": [6, 2, null, 4, 5, null, null, 2, 1] + }, { + "backgroundColor": "rgba(255, 0, 0, 0.25)", + "data": [7, 3, 4, 5, 6, 1, 4, null, null] + }, { + "backgroundColor": "rgba(0, 0, 255, 0.25)", + "data": [8, 7, 6, -6, -4, -6, 4, 5, 8] + }] + }, + "options": { + "responsive": false, + "spanGaps": true, + "legend": false, + "title": false, + "scales": { + "xAxes": [{ + "ticks": { + "display": false + } + }], + "yAxes": [{ + "ticks": { + "display": false + } + }] + }, + "elements": { + "point": { + "radius": 0 + }, + "line": { + "borderColor": "transparent", + "fill": "bottom", + "tension": 0 + } + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 512 + } + } +} diff --git a/test/fixtures/element.line/fill-line-bottom-span.png b/test/fixtures/element.line/fill-line-bottom-span.png new file mode 100644 index 000000000..45e499276 Binary files /dev/null and b/test/fixtures/element.line/fill-line-bottom-span.png differ diff --git a/test/fixtures/element.line/fill-line-bottom.json b/test/fixtures/element.line/fill-line-bottom.json new file mode 100644 index 000000000..a0557fe78 --- /dev/null +++ b/test/fixtures/element.line/fill-line-bottom.json @@ -0,0 +1,55 @@ +{ + "config": { + "type": "line", + "data": { + "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"], + "datasets": [{ + "backgroundColor": "rgba(0, 0, 255, 0.25)", + "data": [null, null, 2, 3, 4, -4, -2, 1, 0] + }, { + "backgroundColor": "rgba(0, 255, 0, 0.25)", + "data": [6, 2, null, 4, 5, null, null, 2, 1] + }, { + "backgroundColor": "rgba(255, 0, 0, 0.25)", + "data": [7, 3, 4, 5, 6, 1, 4, null, null] + }, { + "backgroundColor": "rgba(0, 0, 255, 0.25)", + "data": [8, 7, 6, -6, -4, -6, 4, 5, 8] + }] + }, + "options": { + "responsive": false, + "spanGaps": false, + "legend": false, + "title": false, + "scales": { + "xAxes": [{ + "ticks": { + "display": false + } + }], + "yAxes": [{ + "ticks": { + "display": false + } + }] + }, + "elements": { + "point": { + "radius": 0 + }, + "line": { + "borderColor": "transparent", + "fill": "bottom", + "tension": 0 + } + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 512 + } + } +} diff --git a/test/fixtures/element.line/fill-line-bottom.png b/test/fixtures/element.line/fill-line-bottom.png new file mode 100644 index 000000000..1cd05e60d Binary files /dev/null and b/test/fixtures/element.line/fill-line-bottom.png differ diff --git a/test/fixtures/element.line/fill-line-spline-span.json b/test/fixtures/element.line/fill-line-spline-span.json new file mode 100644 index 000000000..c87833b6e --- /dev/null +++ b/test/fixtures/element.line/fill-line-spline-span.json @@ -0,0 +1,55 @@ +{ + "config": { + "type": "line", + "data": { + "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"], + "datasets": [{ + "backgroundColor": "rgba(0, 0, 192, 0.25)", + "data": [null, null, 2, 4, 2, 1, -1, 1, 2] + }, { + "backgroundColor": "rgba(0, 192, 0, 0.25)", + "data": [4, 2, null, 3, 2.5, null, -2, 1.5, 3] + }, { + "backgroundColor": "rgba(192, 0, 0, 0.25)", + "data": [3.5, 2, 1, 2.5, -2, 3, -1, null, null] + }, { + "backgroundColor": "rgba(128, 0, 128, 0.25)", + "data": [5, 6, 5, -2, -4, -3, 4, 2, 4.5] + }] + }, + "options": { + "responsive": false, + "spanGaps": true, + "legend": false, + "title": false, + "scales": { + "xAxes": [{ + "ticks": { + "display": false + } + }], + "yAxes": [{ + "ticks": { + "display": false + } + }] + }, + "elements": { + "point": { + "radius": 0 + }, + "line": { + "cubicInterpolationMode": "monotone", + "borderColor": "transparent", + "fill": "zero" + } + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 512 + } + } +} diff --git a/test/fixtures/element.line/fill-line-spline-span.png b/test/fixtures/element.line/fill-line-spline-span.png new file mode 100644 index 000000000..01aae5a99 Binary files /dev/null and b/test/fixtures/element.line/fill-line-spline-span.png differ diff --git a/test/fixtures/element.line/fill-line-spline.json b/test/fixtures/element.line/fill-line-spline.json new file mode 100644 index 000000000..543b8cd06 --- /dev/null +++ b/test/fixtures/element.line/fill-line-spline.json @@ -0,0 +1,55 @@ +{ + "config": { + "type": "line", + "data": { + "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"], + "datasets": [{ + "backgroundColor": "rgba(0, 0, 192, 0.25)", + "data": [null, null, 2, 4, 2, 1, -1, 1, 2] + }, { + "backgroundColor": "rgba(0, 192, 0, 0.25)", + "data": [4, 2, null, 3, 2.5, null, -2, 1.5, 3] + }, { + "backgroundColor": "rgba(192, 0, 0, 0.25)", + "data": [3.5, 2, 1, 2.5, -2, 3, -1, null, null] + }, { + "backgroundColor": "rgba(128, 0, 128, 0.25)", + "data": [5, 6, 5, -2, -4, -3, 4, 2, 4.5] + }] + }, + "options": { + "responsive": false, + "spanGaps": false, + "legend": false, + "title": false, + "scales": { + "xAxes": [{ + "ticks": { + "display": false + } + }], + "yAxes": [{ + "ticks": { + "display": false + } + }] + }, + "elements": { + "point": { + "radius": 0 + }, + "line": { + "cubicInterpolationMode": "monotone", + "borderColor": "transparent", + "fill": "zero" + } + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 512 + } + } +} diff --git a/test/fixtures/element.line/fill-line-spline.png b/test/fixtures/element.line/fill-line-spline.png new file mode 100644 index 000000000..b2ec1797f Binary files /dev/null and b/test/fixtures/element.line/fill-line-spline.png differ diff --git a/test/fixtures/element.line/fill-line-stepped-span.json b/test/fixtures/element.line/fill-line-stepped-span.json new file mode 100644 index 000000000..c6f24e1c1 --- /dev/null +++ b/test/fixtures/element.line/fill-line-stepped-span.json @@ -0,0 +1,56 @@ +{ + "config": { + "type": "line", + "data": { + "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"], + "datasets": [{ + "backgroundColor": "rgba(0, 0, 192, 0.25)", + "data": [null, null, 2, 4, 2, 1, -1, 1, 2] + }, { + "backgroundColor": "rgba(0, 192, 0, 0.25)", + "data": [4, 2, null, 3, 2.5, null, -2, 1.5, 3] + }, { + "backgroundColor": "rgba(192, 0, 0, 0.25)", + "data": [3.5, 2, 1, 2.5, -2, 3, -1, null, null] + }, { + "backgroundColor": "rgba(128, 0, 128, 0.25)", + "data": [5, 6, 5, -2, -4, -3, 4, 2, 4.5] + }] + }, + "options": { + "responsive": false, + "spanGaps": true, + "legend": false, + "title": false, + "scales": { + "xAxes": [{ + "ticks": { + "display": false + } + }], + "yAxes": [{ + "ticks": { + "display": false + } + }] + }, + "elements": { + "point": { + "radius": 0 + }, + "line": { + "cubicInterpolationMode": "monotone", + "borderColor": "transparent", + "stepped": true, + "fill": "zero" + } + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 512 + } + } +} diff --git a/test/fixtures/element.line/fill-line-stepped-span.png b/test/fixtures/element.line/fill-line-stepped-span.png new file mode 100644 index 000000000..9f7c15b9d Binary files /dev/null and b/test/fixtures/element.line/fill-line-stepped-span.png differ diff --git a/test/fixtures/element.line/fill-line-stepped.json b/test/fixtures/element.line/fill-line-stepped.json new file mode 100644 index 000000000..3fbdd7677 --- /dev/null +++ b/test/fixtures/element.line/fill-line-stepped.json @@ -0,0 +1,56 @@ +{ + "config": { + "type": "line", + "data": { + "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"], + "datasets": [{ + "backgroundColor": "rgba(0, 0, 192, 0.25)", + "data": [null, null, 2, 4, 2, 1, -1, 1, 2] + }, { + "backgroundColor": "rgba(0, 192, 0, 0.25)", + "data": [4, 2, null, 3, 2.5, null, -2, 1.5, 3] + }, { + "backgroundColor": "rgba(192, 0, 0, 0.25)", + "data": [3.5, 2, 1, 2.5, -2, 3, -1, null, null] + }, { + "backgroundColor": "rgba(128, 0, 128, 0.25)", + "data": [5, 6, 5, -2, -4, -3, 4, 2, 4.5] + }] + }, + "options": { + "responsive": false, + "spanGaps": false, + "legend": false, + "title": false, + "scales": { + "xAxes": [{ + "ticks": { + "display": false + } + }], + "yAxes": [{ + "ticks": { + "display": false + } + }] + }, + "elements": { + "point": { + "radius": 0 + }, + "line": { + "cubicInterpolationMode": "monotone", + "borderColor": "transparent", + "stepped": true, + "fill": "zero" + } + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 512 + } + } +} diff --git a/test/fixtures/element.line/fill-line-stepped.png b/test/fixtures/element.line/fill-line-stepped.png new file mode 100644 index 000000000..19f5a8d92 Binary files /dev/null and b/test/fixtures/element.line/fill-line-stepped.png differ diff --git a/test/fixtures/element.line/fill-line-top-span.json b/test/fixtures/element.line/fill-line-top-span.json new file mode 100644 index 000000000..b74b1f5ca --- /dev/null +++ b/test/fixtures/element.line/fill-line-top-span.json @@ -0,0 +1,55 @@ +{ + "config": { + "type": "line", + "data": { + "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"], + "datasets": [{ + "backgroundColor": "rgba(0, 0, 192, 0.25)", + "data": [null, null, 2, 3, 4, -4, -2, 1, 0] + }, { + "backgroundColor": "rgba(0, 192, 0, 0.25)", + "data": [5.5, 2, null, 4, 5, null, null, 2, 1] + }, { + "backgroundColor": "rgba(192, 0, 0, 0.25)", + "data": [7, 3, 4, 5, 6, 1, 4, null, null] + }, { + "backgroundColor": "rgba(0, 0, 192, 0.25)", + "data": [8, 7, 6.5, -6, -4, -6, 4, 5, 8] + }] + }, + "options": { + "responsive": false, + "spanGaps": true, + "legend": false, + "title": false, + "scales": { + "xAxes": [{ + "ticks": { + "display": false + } + }], + "yAxes": [{ + "ticks": { + "display": false + } + }] + }, + "elements": { + "point": { + "radius": 0 + }, + "line": { + "borderColor": "transparent", + "fill": "top", + "tension": 0 + } + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 512 + } + } +} diff --git a/test/fixtures/element.line/fill-line-top-span.png b/test/fixtures/element.line/fill-line-top-span.png new file mode 100644 index 000000000..83519b05f Binary files /dev/null and b/test/fixtures/element.line/fill-line-top-span.png differ diff --git a/test/fixtures/element.line/fill-line-top.json b/test/fixtures/element.line/fill-line-top.json new file mode 100644 index 000000000..e0696ba06 --- /dev/null +++ b/test/fixtures/element.line/fill-line-top.json @@ -0,0 +1,55 @@ +{ + "config": { + "type": "line", + "data": { + "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"], + "datasets": [{ + "backgroundColor": "rgba(0, 0, 192, 0.25)", + "data": [null, null, 2, 3, 4, -4, -2, 1, 0] + }, { + "backgroundColor": "rgba(0, 192, 0, 0.25)", + "data": [5.5, 2, null, 4, 5, null, null, 2, 1] + }, { + "backgroundColor": "rgba(192, 0, 0, 0.25)", + "data": [7, 3, 4, 5, 6, 1, 4, null, null] + }, { + "backgroundColor": "rgba(0, 0, 192, 0.25)", + "data": [8, 7, 6.5, -6, -4, -6, 4, 5, 8] + }] + }, + "options": { + "responsive": false, + "spanGaps": false, + "legend": false, + "title": false, + "scales": { + "xAxes": [{ + "ticks": { + "display": false + } + }], + "yAxes": [{ + "ticks": { + "display": false + } + }] + }, + "elements": { + "point": { + "radius": 0 + }, + "line": { + "borderColor": "transparent", + "fill": "top", + "tension": 0 + } + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 512 + } + } +} diff --git a/test/fixtures/element.line/fill-line-top.png b/test/fixtures/element.line/fill-line-top.png new file mode 100644 index 000000000..25c8f1369 Binary files /dev/null and b/test/fixtures/element.line/fill-line-top.png differ diff --git a/test/fixtures/element.line/fill-line-zero-span.json b/test/fixtures/element.line/fill-line-zero-span.json new file mode 100644 index 000000000..64fbbb315 --- /dev/null +++ b/test/fixtures/element.line/fill-line-zero-span.json @@ -0,0 +1,55 @@ +{ + "config": { + "type": "line", + "data": { + "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"], + "datasets": [{ + "backgroundColor": "rgba(0, 0, 192, 0.25)", + "data": [null, null, 2, 3, 4, -4, -2, 1, 0] + }, { + "backgroundColor": "rgba(0, 192, 0, 0.25)", + "data": [6, 2, null, 4, 5, null, null, 2, 1] + }, { + "backgroundColor": "rgba(192, 0, 0, 0.25)", + "data": [7, 3, 4, 5, 6, 1, 4, null, null] + }, { + "backgroundColor": "rgba(0, 64, 192, 0.25)", + "data": [8, 7, 6, -6, -4, -6, 4, 5, 8] + }] + }, + "options": { + "responsive": false, + "spanGaps": true, + "legend": false, + "title": false, + "scales": { + "xAxes": [{ + "ticks": { + "display": false + } + }], + "yAxes": [{ + "ticks": { + "display": false + } + }] + }, + "elements": { + "point": { + "radius": 0 + }, + "line": { + "borderColor": "transparent", + "fill": "zero", + "tension": 0 + } + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 512 + } + } +} diff --git a/test/fixtures/element.line/fill-line-zero-span.png b/test/fixtures/element.line/fill-line-zero-span.png new file mode 100644 index 000000000..727cc0309 Binary files /dev/null and b/test/fixtures/element.line/fill-line-zero-span.png differ diff --git a/test/fixtures/element.line/fill-line-zero.json b/test/fixtures/element.line/fill-line-zero.json new file mode 100644 index 000000000..1958fc5d0 --- /dev/null +++ b/test/fixtures/element.line/fill-line-zero.json @@ -0,0 +1,55 @@ +{ + "config": { + "type": "line", + "data": { + "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"], + "datasets": [{ + "backgroundColor": "rgba(0, 0, 192, 0.25)", + "data": [null, null, 2, 3, 4, -4, -2, 1, 0] + }, { + "backgroundColor": "rgba(0, 192, 0, 0.25)", + "data": [6, 2, null, 4, 5, null, null, 2, 1] + }, { + "backgroundColor": "rgba(192, 0, 0, 0.25)", + "data": [7, 3, 4, 5, 6, 1, 4, null, null] + }, { + "backgroundColor": "rgba(0, 64, 192, 0.25)", + "data": [8, 7, 6, -6, -4, -6, 4, 5, 8] + }] + }, + "options": { + "responsive": false, + "spanGaps": false, + "legend": false, + "title": false, + "scales": { + "xAxes": [{ + "ticks": { + "display": false + } + }], + "yAxes": [{ + "ticks": { + "display": false + } + }] + }, + "elements": { + "point": { + "radius": 0 + }, + "line": { + "borderColor": "transparent", + "fill": "zero", + "tension": 0 + } + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 512 + } + } +} diff --git a/test/fixtures/element.line/fill-line-zero.png b/test/fixtures/element.line/fill-line-zero.png new file mode 100644 index 000000000..f8f0208d4 Binary files /dev/null and b/test/fixtures/element.line/fill-line-zero.png differ diff --git a/test/fixtures/element.line/fill-radar-spline.json b/test/fixtures/element.line/fill-radar-spline.json new file mode 100644 index 000000000..c22d8a651 --- /dev/null +++ b/test/fixtures/element.line/fill-radar-spline.json @@ -0,0 +1,50 @@ +{ + "config": { + "type": "radar", + "data": { + "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"], + "datasets": [{ + "backgroundColor": "rgba(0, 0, 192, 0.25)", + "data": [null, null, 2, 4, 2, 1, -1, 1, 2] + }, { + "backgroundColor": "rgba(0, 192, 0, 0.25)", + "data": [4, 2, null, 3, 2.5, null, -2, 1.5, 3] + }, { + "backgroundColor": "rgba(192, 0, 0, 0.25)", + "data": [3.5, 2, 1, 2.5, -2, 3, -1, null, null] + }, { + "backgroundColor": "rgba(128, 0, 128, 0.25)", + "data": [5, 6, 5, -2, -4, -3, 4, 2, 4.5] + }] + }, + "options": { + "responsive": false, + "legend": false, + "title": false, + "scale": { + "pointLabels": { + "fontSize": 0 + }, + "ticks": { + "display": false + } + }, + "elements": { + "point": { + "radius": 0 + }, + "line": { + "borderColor": "transparent", + "tension": 0.5, + "fill": "zero" + } + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 256 + } + } +} diff --git a/test/fixtures/element.line/fill-radar-spline.png b/test/fixtures/element.line/fill-radar-spline.png new file mode 100644 index 000000000..29b6c8e18 Binary files /dev/null and b/test/fixtures/element.line/fill-radar-spline.png differ diff --git a/test/fixtures/element.line/fill-radar-zero.json b/test/fixtures/element.line/fill-radar-zero.json new file mode 100644 index 000000000..172007943 --- /dev/null +++ b/test/fixtures/element.line/fill-radar-zero.json @@ -0,0 +1,49 @@ +{ + "config": { + "type": "radar", + "data": { + "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"], + "datasets": [{ + "backgroundColor": "rgba(0, 0, 192, 0.25)", + "data": [null, null, 2, 4, 2, 1, -1, 1, 2] + }, { + "backgroundColor": "rgba(0, 192, 0, 0.25)", + "data": [4, 2, null, 3, 2.5, null, -2, 1.5, 3] + }, { + "backgroundColor": "rgba(192, 0, 0, 0.25)", + "data": [3.5, 2, 1, 2.5, -2, 3, -1, null, null] + }, { + "backgroundColor": "rgba(128, 0, 128, 0.25)", + "data": [5, 6, 5, -2, -4, -3, 4, 2, 4.5] + }] + }, + "options": { + "responsive": false, + "legend": false, + "title": false, + "scale": { + "pointLabels": { + "fontSize": 0 + }, + "ticks": { + "display": false + } + }, + "elements": { + "point": { + "radius": 0 + }, + "line": { + "borderColor": "transparent", + "fill": "zero" + } + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 256 + } + } +} diff --git a/test/fixtures/element.line/fill-radar-zero.png b/test/fixtures/element.line/fill-radar-zero.png new file mode 100644 index 000000000..5ede186f2 Binary files /dev/null and b/test/fixtures/element.line/fill-radar-zero.png differ diff --git a/test/jasmine.index.js b/test/jasmine.index.js index aeaee77c6..55c587ab0 100644 --- a/test/jasmine.index.js +++ b/test/jasmine.index.js @@ -37,6 +37,8 @@ var utils = require('./jasmine.utils'); 'position: absolute' + '}'); + jasmine.specsFromFixtures = utils.specsFromFixtures; + beforeEach(function() { jasmine.addMatchers(matchers); }); diff --git a/test/jasmine.matchers.js b/test/jasmine.matchers.js index abb90d778..8a3c9e121 100644 --- a/test/jasmine.matchers.js +++ b/test/jasmine.matchers.js @@ -1,5 +1,56 @@ 'use strict'; +var pixelmatch = require('pixelmatch'); +var utils = require('./jasmine.utils'); + +function toPercent(value) { + return Math.round(value * 10000) / 100; +} + +function createImageData(w, h) { + var canvas = utils.createCanvas(w, h); + var context = canvas.getContext('2d'); + return context.getImageData(0, 0, w, h); +} + +function canvasFromImageData(data) { + var canvas = utils.createCanvas(data.width, data.height); + var context = canvas.getContext('2d'); + context.putImageData(data, 0, 0); + return canvas; +} + +function buildPixelMatchPreview(actual, expected, diff, threshold, tolerance, count) { + var ratio = count / (actual.width * actual.height); + var wrapper = document.createElement('div'); + + wrapper.style.cssText = 'display: flex; overflow-y: auto'; + + [ + {data: actual, label: 'Actual'}, + {data: expected, label: 'Expected'}, + {data: diff, label: + 'diff: ' + count + 'px ' + + '(' + toPercent(ratio) + '%)
' + + 'thr: ' + toPercent(threshold) + '%, ' + + 'tol: '+ toPercent(tolerance) + '%' + } + ].forEach(function(values) { + var item = document.createElement('div'); + item.style.cssText = 'text-align: center; font: 12px monospace; line-height: 1.4; margin: 8px'; + item.innerHTML = '
' + values.label + '
'; + item.appendChild(canvasFromImageData(values.data)); + wrapper.appendChild(item); + }); + + // WORKAROUND: https://github.com/karma-runner/karma-jasmine/issues/139 + wrapper.indexOf = function() { + return -1; + }; + + return wrapper; +} + function toBeCloseToPixel() { return { compare: function(actual, expected) { @@ -105,9 +156,50 @@ function toBeChartOfSize() { }; } +function toEqualImageData() { + return { + compare: function(actual, expected, opts) { + var message = null; + var debug = opts.debug || false; + var tolerance = opts.tolerance === undefined? 0.001 : opts.tolerance; + var threshold = opts.threshold === undefined? 0.1 : opts.threshold; + var ctx, idata, ddata, w, h, count, ratio; + + if (actual instanceof Chart) { + ctx = actual.ctx; + } else if (actual instanceof HTMLCanvasElement) { + ctx = actual.getContext('2d'); + } else if (actual instanceof CanvasRenderingContext2D) { + ctx = actual; + } + + if (ctx) { + h = expected.height; + w = expected.width; + idata = ctx.getImageData(0, 0, w, h); + ddata = createImageData(w, h); + count = pixelmatch(idata.data, expected.data, ddata.data, w, h, {threshold: threshold}); + ratio = count / (w * h); + + if ((ratio > tolerance) || debug) { + message = buildPixelMatchPreview(idata, expected, ddata, threshold, tolerance, count); + } + } else { + message = 'Input value is not a valid image source.'; + } + + return { + message: message, + pass: !message + }; + } + }; +} + module.exports = { toBeCloseToPixel: toBeCloseToPixel, toEqualOneOf: toEqualOneOf, toBeValidChart: toBeValidChart, - toBeChartOfSize: toBeChartOfSize + toBeChartOfSize: toBeChartOfSize, + toEqualImageData: toEqualImageData }; diff --git a/test/jasmine.utils.js b/test/jasmine.utils.js index 77f05c6e0..f5ead49ec 100644 --- a/test/jasmine.utils.js +++ b/test/jasmine.utils.js @@ -1,3 +1,40 @@ +/* global __karma__ */ + +function loadJSON(url, callback) { + var request = new XMLHttpRequest(); + request.onreadystatechange = function() { + if (request.readyState === 4) { + return callback(JSON.parse(request.responseText)); + } + }; + + request.overrideMimeType('application/json'); + request.open('GET', url, true); + request.send(null); +} + +function createCanvas(w, h) { + var canvas = document.createElement('canvas'); + canvas.width = w; + canvas.height = h; + return canvas; +} + +function readImageData(url, callback) { + var image = new Image(); + + image.onload = function() { + var h = image.height; + var w = image.width; + var canvas = createCanvas(w, h); + var ctx = canvas.getContext('2d'); + ctx.drawImage(image, 0, 0, w, h); + callback(ctx.getImageData(0, 0, w, h)); + }; + + image.src = url; +} + /** * Injects a new canvas (and div wrapper) and creates teh associated Chart instance * using the given config. Additional options allow tweaking elements generation. @@ -69,8 +106,53 @@ function injectCSS(css) { head.appendChild(style); } +function specFromFixture(description, inputs) { + it(inputs.json, function(done) { + loadJSON(inputs.json, function(json) { + var chart = acquireChart(json.config, json.options); + if (!inputs.png) { + fail('Missing PNG comparison file for ' + inputs.json); + if (!json.debug) { + releaseChart(chart); + } + done(); + } + + readImageData(inputs.png, function(expected) { + expect(chart).toEqualImageData(expected, json); + releaseChart(chart); + done(); + }); + }); + }); +} + +function specsFromFixtures(path) { + var regex = new RegExp('(^/base/test/fixtures/' + path + '.+)\\.(png|json)'); + var inputs = {}; + + Object.keys(__karma__.files || {}).forEach(function(file) { + var matches = file.match(regex); + var name = matches && matches[1]; + var type = matches && matches[2]; + + if (name && type) { + inputs[name] = inputs[name] || {}; + inputs[name][type] = file; + } + }); + + return function() { + Object.keys(inputs).forEach(function(key) { + specFromFixture(key, inputs[key]); + }); + }; +} + module.exports = { injectCSS: injectCSS, + createCanvas: createCanvas, acquireChart: acquireChart, - releaseChart: releaseChart + releaseChart: releaseChart, + specsFromFixtures: specsFromFixtures }; diff --git a/test/specs/element.line.tests.js b/test/specs/element.line.tests.js index 0a2a6eced..52d94cd37 100644 --- a/test/specs/element.line.tests.js +++ b/test/specs/element.line.tests.js @@ -1,5 +1,7 @@ // Tests for the line element -describe('Line element tests', function() { +describe('Chart.elements.Line', function() { + describe('auto', jasmine.specsFromFixtures('element.line')); + it('should be constructed', function() { var line = new Chart.elements.Line({ _datasetindex: 2,