first commit

This commit is contained in:
2026-01-16 14:13:44 +08:00
commit 903ff8d495
34603 changed files with 8585054 additions and 0 deletions

14
WebRoot/node_modules/chart.js/test/.eslintrc.yml generated vendored Normal file
View File

@ -0,0 +1,14 @@
env:
jasmine: true
globals:
acquireChart: true
Chart: true
moment: true
waitForResize: true
# http://eslint.org/docs/rules/
rules:
# Best Practices
complexity: 0
max-statements: 0

View File

@ -0,0 +1,42 @@
{
"config": {
"type": "bar",
"data": {
"labels": ["2017", "2018", "2019", "2024", "2025"],
"datasets": [{
"backgroundColor": "rgba(255, 99, 132, 0.5)",
"data": [1, null, 3, 4, 5]
}]
},
"options": {
"responsive": false,
"legend": false,
"title": false,
"scales": {
"xAxes": [{
"type": "time",
"offset": true,
"display": false,
"barPercentage": 1,
"categoryPercentage": 1,
"barThickness": 128,
"ticks": {
"source": "labels"
}
}],
"yAxes": [{
"display": false,
"ticks": {
"beginAtZero": true
}
}]
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1,42 @@
{
"config": {
"type": "bar",
"data": {
"labels": ["2017", "2018", "2020", "2024", "2038"],
"datasets": [{
"backgroundColor": "#FF6384",
"data": [1, null, 3, 4, 5]
}]
},
"options": {
"responsive": false,
"legend": false,
"title": false,
"scales": {
"xAxes": [{
"type": "time",
"offset": true,
"display": false,
"barPercentage": 1,
"categoryPercentage": 1,
"barThickness": "flex",
"ticks": {
"source": "labels"
}
}],
"yAxes": [{
"display": false,
"ticks": {
"beginAtZero": true
}
}]
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -0,0 +1,41 @@
{
"config": {
"type": "bar",
"data": {
"labels": ["2017", "2018", "2020", "2024", "2038"],
"datasets": [{
"backgroundColor": "#FF6384",
"data": [1, null, 3, 4, 5]
}]
},
"options": {
"responsive": false,
"legend": false,
"title": false,
"scales": {
"xAxes": [{
"type": "time",
"display": false,
"barPercentage": 1,
"categoryPercentage": 1,
"barThickness": "flex",
"ticks": {
"source": "labels"
}
}],
"yAxes": [{
"display": false,
"ticks": {
"beginAtZero": true
}
}]
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -0,0 +1,41 @@
{
"config": {
"type": "bar",
"data": {
"labels": ["2016", "2018", "2020", "2024", "2030"],
"datasets": [{
"backgroundColor": "#FF6384",
"data": [1, null, 3, 4, 5]
}]
},
"options": {
"responsive": false,
"legend": false,
"title": false,
"scales": {
"xAxes": [{
"type": "time",
"display": false,
"barPercentage": 1,
"categoryPercentage": 1,
"maxBarThickness": 8,
"ticks": {
"source": "labels"
}
}],
"yAxes": [{
"display": false,
"ticks": {
"beginAtZero": true
}
}]
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1,40 @@
{
"config": {
"type": "bar",
"data": {
"labels": ["2016", "2018", "2020", "2024", "2030"],
"datasets": [{
"backgroundColor": "#FF6384",
"data": [1, null, 3, 4, 5]
}]
},
"options": {
"responsive": false,
"legend": false,
"title": false,
"scales": {
"xAxes": [{
"type": "time",
"display": false,
"barPercentage": 1,
"categoryPercentage": 1,
"ticks": {
"source": "labels"
}
}],
"yAxes": [{
"display": false,
"ticks": {
"beginAtZero": true
}
}]
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -0,0 +1,46 @@
{
"config": {
"type": "bar",
"data": {
"labels": ["2016", "2018", "2020", "2024", "2030"],
"datasets": [{
"backgroundColor": "#FF6384",
"data": [1, null, 3, 4, 5]
}, {
"backgroundColor": "#36A2EB",
"data": [5, 4, 3, null, 1]
}, {
"backgroundColor": "#FFCE56",
"data": [3, 5, 2, null, 4]
}]
},
"options": {
"responsive": false,
"legend": false,
"title": false,
"scales": {
"xAxes": [{
"type": "time",
"display": false,
"barPercentage": 1,
"categoryPercentage": 1,
"ticks": {
"source": "labels"
}
}],
"yAxes": [{
"display": false,
"ticks": {
"beginAtZero": true
}
}]
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -0,0 +1,46 @@
{
"config": {
"type": "bar",
"data": {
"labels": ["2016", "2018", "2020", "2024", "2030"],
"datasets": [{
"backgroundColor": "#FF6384",
"data": [
{"y": "1", "t": "2016"},
{"y": "2", "t": "2017"},
{"y": "3", "t": "2017-08"},
{"y": "4", "t": "2024"},
{"y": "5", "t": "2030"}
]
}]
},
"options": {
"responsive": false,
"legend": false,
"title": false,
"scales": {
"xAxes": [{
"type": "time",
"display": false,
"barPercentage": 1,
"categoryPercentage": 1,
"ticks": {
"source": "labels"
}
}],
"yAxes": [{
"display": false,
"ticks": {
"beginAtZero": true
}
}]
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,47 @@
{
"config": {
"type": "bar",
"data": {
"labels": ["2016", "2018", "2020", "2024", "2030"],
"datasets": [{
"backgroundColor": "#FF6384",
"data": [1, null, 3, 4, 5]
}, {
"backgroundColor": "#36A2EB",
"data": [5, 4, 3, null, 1]
}, {
"backgroundColor": "#FFCE56",
"data": [3, 5, 2, null, 4]
}]
},
"options": {
"responsive": false,
"legend": false,
"title": false,
"scales": {
"xAxes": [{
"type": "time",
"offset": true,
"display": false,
"barPercentage": 1,
"categoryPercentage": 1,
"ticks": {
"source": "labels"
}
}],
"yAxes": [{
"display": false,
"ticks": {
"beginAtZero": true
}
}]
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -0,0 +1,40 @@
{
"config": {
"type": "bar",
"data": {
"labels": ["2016", "2018", "2020", "2024", "2030"],
"datasets": [{
"backgroundColor": "#FF6384",
"data": [{"x": "2022", "y": 42}]
}]
},
"options": {
"responsive": false,
"legend": false,
"title": false,
"scales": {
"xAxes": [{
"type": "time",
"display": false,
"barPercentage": 1,
"categoryPercentage": 1,
"ticks": {
"source": "labels"
}
}],
"yAxes": [{
"display": false,
"ticks": {
"beginAtZero": true
}
}]
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -0,0 +1,43 @@
{
"config": {
"type": "bar",
"data": {
"labels": ["2016", "2018", "2020", "2024", "2030"],
"datasets": [{
"backgroundColor": "#FF6384",
"data": [1]
}]
},
"options": {
"responsive": false,
"legend": false,
"title": false,
"scales": {
"xAxes": [{
"type": "time",
"display": false,
"barPercentage": 1,
"categoryPercentage": 1,
"ticks": {
"source": "labels"
},
"time": {
"min": "2013"
}
}],
"yAxes": [{
"display": false,
"ticks": {
"beginAtZero": true
}
}]
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1,48 @@
{
"config": {
"type": "bar",
"data": {
"labels": ["2016", "2018", "2020", "2024", "2030"],
"datasets": [{
"backgroundColor": "#FF6384",
"data": [1, null, 3, 4, 5]
}, {
"backgroundColor": "#36A2EB",
"data": [5, 4, 3, null, 1]
}, {
"backgroundColor": "#FFCE56",
"data": [3, 5, 2, null, 4]
}]
},
"options": {
"responsive": false,
"legend": false,
"title": false,
"scales": {
"xAxes": [{
"type": "time",
"stacked": true,
"display": false,
"barPercentage": 1,
"categoryPercentage": 1,
"ticks": {
"source": "labels"
}
}],
"yAxes": [{
"display": false,
"stacked": true,
"ticks": {
"beginAtZero": true
}
}]
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -0,0 +1,41 @@
{
"config": {
"type": "horizontalBar",
"data": {
"labels": ["\u25C0", "\u25A0", "\u25C6", "\u25CF"],
"datasets": [{
"data": [12, 19, 3, 5]
}]
},
"options": {
"legend": false,
"title": false,
"scales": {
"xAxes": [{
"ticks": {
"display": false
},
"gridLines":{
"display": false,
"drawBorder": false
}
}],
"yAxes": [{
"ticks": {
"labelOffset": 25
},
"gridLines":{
"display": false,
"drawBorder": false
}
}]
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1,51 @@
{
"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": [{
"display": false
}],
"yAxes": [{
"display": false
}]
},
"elements": {
"point": {
"radius": 0
},
"line": {
"borderColor": "transparent",
"fill": "end",
"tension": 0
}
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,51 @@
{
"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": [{
"display": false
}],
"yAxes": [{
"display": false
}]
},
"elements": {
"point": {
"radius": 0
},
"line": {
"borderColor": "transparent",
"fill": "end",
"tension": 0
}
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,51 @@
{
"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": [{
"display": false
}],
"yAxes": [{
"display": false
}]
},
"elements": {
"point": {
"radius": 0
},
"line": {
"borderColor": "transparent",
"fill": "origin",
"tension": 0
}
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,51 @@
{
"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": [{
"display": false
}],
"yAxes": [{
"display": false
}]
},
"elements": {
"point": {
"radius": 0
},
"line": {
"cubicInterpolationMode": "monotone",
"borderColor": "transparent",
"fill": "origin"
}
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1,51 @@
{
"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": [{
"display": false
}],
"yAxes": [{
"display": false
}]
},
"elements": {
"point": {
"radius": 0
},
"line": {
"cubicInterpolationMode": "monotone",
"borderColor": "transparent",
"fill": "origin"
}
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,52 @@
{
"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": [{
"display": false
}],
"yAxes": [{
"display": false
}]
},
"elements": {
"point": {
"radius": 0
},
"line": {
"cubicInterpolationMode": "monotone",
"borderColor": "transparent",
"stepped": true,
"fill": "origin"
}
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -0,0 +1,52 @@
{
"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": [{
"display": false
}],
"yAxes": [{
"display": false
}]
},
"elements": {
"point": {
"radius": 0
},
"line": {
"cubicInterpolationMode": "monotone",
"borderColor": "transparent",
"stepped": true,
"fill": "origin"
}
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,51 @@
{
"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": [{
"display": false
}],
"yAxes": [{
"display": false
}]
},
"elements": {
"point": {
"radius": 0
},
"line": {
"borderColor": "transparent",
"fill": "origin",
"tension": 0
}
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,51 @@
{
"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": [{
"display": false
}],
"yAxes": [{
"display": false
}]
},
"elements": {
"point": {
"radius": 0
},
"line": {
"borderColor": "transparent",
"fill": "start",
"tension": 0
}
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,51 @@
{
"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": [{
"display": false
}],
"yAxes": [{
"display": false
}]
},
"elements": {
"point": {
"radius": 0
},
"line": {
"borderColor": "transparent",
"fill": "start",
"tension": 0
}
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,58 @@
{
"config": {
"type": "line",
"data": {
"labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"],
"datasets": [{
"backgroundColor": "rgba(255, 0, 0, 0.25)",
"data": [null, null, 0, -1, 0, 1, 0, -1, 0],
"fill": 1
}, {
"backgroundColor": "rgba(0, 255, 0, 0.25)",
"data": [1, 0, null, 1, 0, null, -1, 0, 1],
"fill": "+1"
}, {
"backgroundColor": "rgba(0, 0, 255, 0.25)",
"data": [0, 2, 0, -2, 0, 2, 0],
"fill": 3
}, {
"backgroundColor": "rgba(255, 0, 255, 0.25)",
"data": [2, 0, -2, 0, 2, 0, -2, 0, 2],
"fill": "-2"
}, {
"backgroundColor": "rgba(255, 255, 0, 0.25)",
"data": [3, 1, -1, -3, -1, 1, 3, 1, -1],
"fill": "-1"
}]
},
"options": {
"responsive": false,
"spanGaps": true,
"legend": false,
"title": false,
"scales": {
"xAxes": [{
"display": false
}],
"yAxes": [{
"display": false
}]
},
"elements": {
"point": {
"radius": 0
},
"line": {
"borderColor": "transparent",
"tension": 0
}
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,58 @@
{
"config": {
"type": "line",
"data": {
"labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"],
"datasets": [{
"backgroundColor": "rgba(255, 0, 0, 0.25)",
"data": [null, null, 0, -1, 0, 1, 0, -1, 0],
"fill": 1
}, {
"backgroundColor": "rgba(0, 255, 0, 0.25)",
"data": [1, 0, null, 1, 0, null, -1, 0, 1],
"fill": "+1"
}, {
"backgroundColor": "rgba(0, 0, 255, 0.25)",
"data": [0, 2, 0, -2, 0, 2, 0],
"fill": 3
}, {
"backgroundColor": "rgba(255, 0, 255, 0.25)",
"data": [2, 0, -2, 0, 2, 0, -2, 0, 2],
"fill": "-2"
}, {
"backgroundColor": "rgba(255, 255, 0, 0.25)",
"data": [3, 1, -1, -3, -1, 1, 3, 1, -1],
"fill": "-1"
}]
},
"options": {
"responsive": false,
"spanGaps": true,
"legend": false,
"title": false,
"scales": {
"xAxes": [{
"display": false
}],
"yAxes": [{
"display": false
}]
},
"elements": {
"point": {
"radius": 0
},
"line": {
"cubicInterpolationMode": "monotone",
"borderColor": "transparent"
}
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -0,0 +1,58 @@
{
"config": {
"type": "line",
"data": {
"labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"],
"datasets": [{
"backgroundColor": "rgba(255, 0, 0, 0.25)",
"data": [null, null, 0, -1, 0, 1, 0, -1, 0],
"fill": 1
}, {
"backgroundColor": "rgba(0, 255, 0, 0.25)",
"data": [1, 0, null, 1, 0, null, -1, 0, 1],
"fill": "+1"
}, {
"backgroundColor": "rgba(0, 0, 255, 0.25)",
"data": [0, 2, 0, -2, 0, 2, 0],
"fill": 3
}, {
"backgroundColor": "rgba(255, 0, 255, 0.25)",
"data": [2, 0, -2, 0, 2, 0, -2, 0, 2],
"fill": "-2"
}, {
"backgroundColor": "rgba(255, 255, 0, 0.25)",
"data": [3, 1, -1, -3, -1, 1, 3, 1, -1],
"fill": "-1"
}]
},
"options": {
"responsive": false,
"spanGaps": false,
"legend": false,
"title": false,
"scales": {
"xAxes": [{
"display": false
}],
"yAxes": [{
"display": false
}]
},
"elements": {
"point": {
"radius": 0
},
"line": {
"cubicInterpolationMode": "monotone",
"borderColor": "transparent"
}
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,58 @@
{
"config": {
"type": "line",
"data": {
"labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"],
"datasets": [{
"backgroundColor": "rgba(255, 0, 0, 0.25)",
"data": [null, null, 0, -1, 0, 1, 0, -1, 0],
"fill": 1
}, {
"backgroundColor": "rgba(0, 255, 0, 0.25)",
"data": [1, 0, null, 1, 0, null, -1, 0, 1],
"fill": "+1"
}, {
"backgroundColor": "rgba(0, 0, 255, 0.25)",
"data": [0, 2, 0, -2, 0, 2, 0],
"fill": 3
}, {
"backgroundColor": "rgba(255, 0, 255, 0.25)",
"data": [2, 0, -2, 0, 2, 0, -2, 0, 2],
"fill": "-2"
}, {
"backgroundColor": "rgba(255, 255, 0, 0.25)",
"data": [3, 1, -1, -3, -1, 1, 3, 1, -1],
"fill": "-1"
}]
},
"options": {
"responsive": false,
"spanGaps": false,
"legend": false,
"title": false,
"scales": {
"xAxes": [{
"display": false
}],
"yAxes": [{
"display": false
}]
},
"elements": {
"point": {
"radius": 0
},
"line": {
"borderColor": "transparent",
"tension": 0
}
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1,45 @@
{
"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": {
"display": false
},
"elements": {
"point": {
"radius": 0
},
"line": {
"borderColor": "transparent",
"tension": 0.5,
"fill": "origin"
}
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 256
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,44 @@
{
"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": {
"display": false
},
"elements": {
"point": {
"radius": 0
},
"line": {
"borderColor": "transparent",
"fill": "origin"
}
}
}
},
"options": {
"canvas": {
"height": 256,
"width": 256
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

126
WebRoot/node_modules/chart.js/test/jasmine.context.js generated vendored Normal file
View File

@ -0,0 +1,126 @@
// 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() {},
rect: 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;

61
WebRoot/node_modules/chart.js/test/jasmine.index.js generated vendored Normal file
View File

@ -0,0 +1,61 @@
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();
}
// force ratio=1 for tests on high-res/retina devices
// fixes https://github.com/chartjs/Chart.js/issues/4515
window.devicePixelRatio = 1;
window.acquireChart = acquireChart;
window.releaseChart = releaseChart;
window.waitForResize = utils.waitForResize;
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' +
'}');
jasmine.specsFromFixtures = utils.specsFromFixtures;
jasmine.triggerMouseEvent = utils.triggerMouseEvent;
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);
}
});
});
}());

205
WebRoot/node_modules/chart.js/test/jasmine.matchers.js generated vendored Normal file
View File

@ -0,0 +1,205 @@
'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) + '%)<br/>' +
'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 = '<div style="margin: 8px; height: 32px">' + values.label + '</div>';
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) {
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 (Object.prototype.toString.call(actual.canvas) !== '[object HTMLCanvasElement]') {
message = 'Expected canvas to be an instance of HTMLCanvasElement';
} else if (Object.prototype.toString.call(actual.ctx) !== '[object 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 = actual.options.devicePixelRatio || window.devicePixelRatio;
var dh = parseInt(style.height, 10) || 0;
var dw = parseInt(style.width, 10) || 0;
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
};
}
};
}
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,
toEqualImageData: toEqualImageData
};

186
WebRoot/node_modules/chart.js/test/jasmine.utils.js generated vendored Normal file
View File

@ -0,0 +1,186 @@
/* 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.
* @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);
try {
chart = new Chart(canvas.getContext('2d'), config);
} catch (e) {
window.document.body.removeChild(wrapper);
throw e;
}
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);
}
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);
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]);
});
};
}
function waitForResize(chart, callback) {
var override = chart.resize;
chart.resize = function() {
chart.resize = override;
override.apply(this, arguments);
callback();
};
}
function triggerMouseEvent(chart, type, el) {
var node = chart.canvas;
var rect = node.getBoundingClientRect();
var event = new MouseEvent(type, {
clientX: rect.left + el._model.x,
clientY: rect.top + el._model.y,
cancelable: true,
bubbles: true,
view: window
});
node.dispatchEvent(event);
}
module.exports = {
injectCSS: injectCSS,
createCanvas: createCanvas,
acquireChart: acquireChart,
releaseChart: releaseChart,
specsFromFixtures: specsFromFixtures,
triggerMouseEvent: triggerMouseEvent,
waitForResize: waitForResize
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,413 @@
describe('Chart.controllers.bubble', function() {
it('should be constructed', function() {
var chart = window.acquireChart({
type: 'bubble',
data: {
datasets: [{
data: []
}]
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.type).toBe('bubble');
expect(meta.controller).not.toBe(undefined);
expect(meta.controller.index).toBe(0);
expect(meta.data).toEqual([]);
meta.controller.updateIndex(1);
expect(meta.controller.index).toBe(1);
});
it('should use the first scale IDs if the dataset does not specify them', function() {
var chart = window.acquireChart({
type: 'bubble',
data: {
datasets: [{
data: []
}]
},
options: {
scales: {
xAxes: [{
id: 'firstXScaleID'
}],
yAxes: [{
id: 'firstYScaleID'
}]
}
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.xAxisID).toBe('firstXScaleID');
expect(meta.yAxisID).toBe('firstYScaleID');
});
it('should create point elements for each data item during initialization', function() {
var chart = window.acquireChart({
type: 'bubble',
data: {
datasets: [{
data: [10, 15, 0, -4]
}]
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.data.length).toBe(4); // 4 points created
expect(meta.data[0] instanceof Chart.elements.Point).toBe(true);
expect(meta.data[1] instanceof Chart.elements.Point).toBe(true);
expect(meta.data[2] instanceof Chart.elements.Point).toBe(true);
expect(meta.data[3] instanceof Chart.elements.Point).toBe(true);
});
it('should draw all elements', function() {
var chart = window.acquireChart({
type: 'bubble',
data: {
datasets: [{
data: [10, 15, 0, -4]
}]
},
options: {
animation: false,
showLines: true
}
});
var meta = chart.getDatasetMeta(0);
spyOn(meta.data[0], 'draw');
spyOn(meta.data[1], 'draw');
spyOn(meta.data[2], 'draw');
spyOn(meta.data[3], 'draw');
chart.update();
expect(meta.data[0].draw.calls.count()).toBe(1);
expect(meta.data[1].draw.calls.count()).toBe(1);
expect(meta.data[2].draw.calls.count()).toBe(1);
expect(meta.data[3].draw.calls.count()).toBe(1);
});
it('should update elements when modifying style', function() {
var chart = window.acquireChart({
type: 'bubble',
data: {
datasets: [{
data: [{
x: 10,
y: 10,
r: 5
}, {
x: -15,
y: -10,
r: 1
}, {
x: 0,
y: -9,
r: 2
}, {
x: -4,
y: 10,
r: 1
}]
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
legend: false,
title: false,
scales: {
xAxes: [{
type: 'category',
display: false
}],
yAxes: [{
type: 'linear',
display: false
}]
}
}
});
var meta = chart.getDatasetMeta(0);
[
{r: 5, x: 0, y: 0},
{r: 1, x: 171, y: 512},
{r: 2, x: 341, y: 486},
{r: 1, x: 512, y: 0}
].forEach(function(expected, i) {
expect(meta.data[i]._model.radius).toBe(expected.r);
expect(meta.data[i]._model.x).toBeCloseToPixel(expected.x);
expect(meta.data[i]._model.y).toBeCloseToPixel(expected.y);
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
backgroundColor: Chart.defaults.global.defaultColor,
borderColor: Chart.defaults.global.defaultColor,
borderWidth: 1,
hitRadius: 1,
skip: false
}));
});
// Use dataset level styles for lines & points
chart.data.datasets[0].backgroundColor = 'rgb(98, 98, 98)';
chart.data.datasets[0].borderColor = 'rgb(8, 8, 8)';
chart.data.datasets[0].borderWidth = 0.55;
// point styles
chart.data.datasets[0].radius = 22;
chart.data.datasets[0].hitRadius = 3.3;
chart.update();
for (var i = 0; i < 4; ++i) {
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
backgroundColor: 'rgb(98, 98, 98)',
borderColor: 'rgb(8, 8, 8)',
borderWidth: 0.55,
hitRadius: 3.3,
skip: false
}));
}
// point styles
meta.data[0].custom = {
radius: 2.2,
backgroundColor: 'rgb(0, 1, 3)',
borderColor: 'rgb(4, 6, 8)',
borderWidth: 0.787,
tension: 0.15,
hitRadius: 5,
skip: true
};
chart.update();
expect(meta.data[0]._model).toEqual(jasmine.objectContaining({
backgroundColor: 'rgb(0, 1, 3)',
borderColor: 'rgb(4, 6, 8)',
borderWidth: 0.787,
hitRadius: 5,
skip: true
}));
});
it('should handle number of data point changes in update', function() {
var chart = window.acquireChart({
type: 'bubble',
data: {
datasets: [{
data: [{
x: 10,
y: 10,
r: 5
}, {
x: -15,
y: -10,
r: 1
}, {
x: 0,
y: -9,
r: 2
}, {
x: -4,
y: 10,
r: 1
}]
}],
labels: ['label1', 'label2', 'label3', 'label4']
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.data.length).toBe(4);
chart.data.datasets[0].data = [{
x: 1,
y: 1,
r: 10
}, {
x: 10,
y: 5,
r: 2
}]; // remove 2 items
chart.update();
expect(meta.data.length).toBe(2);
expect(meta.data[0] instanceof Chart.elements.Point).toBe(true);
expect(meta.data[1] instanceof Chart.elements.Point).toBe(true);
chart.data.datasets[0].data = [{
x: 10,
y: 10,
r: 5
}, {
x: -15,
y: -10,
r: 1
}, {
x: 0,
y: -9,
r: 2
}, {
x: -4,
y: 10,
r: 1
}, {
x: -5,
y: 0,
r: 3
}]; // add 3 items
chart.update();
expect(meta.data.length).toBe(5);
expect(meta.data[0] instanceof Chart.elements.Point).toBe(true);
expect(meta.data[1] instanceof Chart.elements.Point).toBe(true);
expect(meta.data[2] instanceof Chart.elements.Point).toBe(true);
expect(meta.data[3] instanceof Chart.elements.Point).toBe(true);
expect(meta.data[4] instanceof Chart.elements.Point).toBe(true);
});
describe('Interactions', function() {
beforeEach(function() {
this.chart = window.acquireChart({
type: 'bubble',
data: {
labels: ['label1', 'label2', 'label3', 'label4'],
datasets: [{
data: [{
x: 5,
y: 5,
r: 20
}, {
x: -15,
y: -10,
r: 15
}, {
x: 15,
y: 10,
r: 10
}, {
x: -15,
y: 10,
r: 5
}]
}]
},
options: {
elements: {
point: {
backgroundColor: 'rgb(100, 150, 200)',
borderColor: 'rgb(50, 100, 150)',
borderWidth: 2,
radius: 3
}
}
}
});
});
it ('should handle default hover styles', function() {
var chart = this.chart;
var point = chart.getDatasetMeta(0).data[0];
jasmine.triggerMouseEvent(chart, 'mousemove', point);
expect(point._model.backgroundColor).toBe('rgb(49, 135, 221)');
expect(point._model.borderColor).toBe('rgb(22, 89, 156)');
expect(point._model.borderWidth).toBe(1);
expect(point._model.radius).toBe(20 + 4);
jasmine.triggerMouseEvent(chart, 'mouseout', point);
expect(point._model.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point._model.borderColor).toBe('rgb(50, 100, 150)');
expect(point._model.borderWidth).toBe(2);
expect(point._model.radius).toBe(20);
});
it ('should handle hover styles defined via dataset properties', function() {
var chart = this.chart;
var point = chart.getDatasetMeta(0).data[0];
Chart.helpers.merge(chart.data.datasets[0], {
hoverBackgroundColor: 'rgb(200, 100, 150)',
hoverBorderColor: 'rgb(150, 50, 100)',
hoverBorderWidth: 8.4,
hoverRadius: 4.2
});
chart.update();
jasmine.triggerMouseEvent(chart, 'mousemove', point);
expect(point._model.backgroundColor).toBe('rgb(200, 100, 150)');
expect(point._model.borderColor).toBe('rgb(150, 50, 100)');
expect(point._model.borderWidth).toBe(8.4);
expect(point._model.radius).toBe(20 + 4.2);
jasmine.triggerMouseEvent(chart, 'mouseout', point);
expect(point._model.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point._model.borderColor).toBe('rgb(50, 100, 150)');
expect(point._model.borderWidth).toBe(2);
expect(point._model.radius).toBe(20);
});
it ('should handle hover styles defined via element options', function() {
var chart = this.chart;
var point = chart.getDatasetMeta(0).data[0];
Chart.helpers.merge(chart.options.elements.point, {
hoverBackgroundColor: 'rgb(200, 100, 150)',
hoverBorderColor: 'rgb(150, 50, 100)',
hoverBorderWidth: 8.4,
hoverRadius: 4.2
});
chart.update();
jasmine.triggerMouseEvent(chart, 'mousemove', point);
expect(point._model.backgroundColor).toBe('rgb(200, 100, 150)');
expect(point._model.borderColor).toBe('rgb(150, 50, 100)');
expect(point._model.borderWidth).toBe(8.4);
expect(point._model.radius).toBe(20 + 4.2);
jasmine.triggerMouseEvent(chart, 'mouseout', point);
expect(point._model.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point._model.borderColor).toBe('rgb(50, 100, 150)');
expect(point._model.borderWidth).toBe(2);
expect(point._model.radius).toBe(20);
});
it ('should handle hover styles defined via element custom', function() {
var chart = this.chart;
var point = chart.getDatasetMeta(0).data[0];
point.custom = {
hoverBackgroundColor: 'rgb(200, 100, 150)',
hoverBorderColor: 'rgb(150, 50, 100)',
hoverBorderWidth: 8.4,
hoverRadius: 4.2
};
chart.update();
jasmine.triggerMouseEvent(chart, 'mousemove', point);
expect(point._model.backgroundColor).toBe('rgb(200, 100, 150)');
expect(point._model.borderColor).toBe('rgb(150, 50, 100)');
expect(point._model.borderWidth).toBe(8.4);
expect(point._model.radius).toBe(20 + 4.2);
jasmine.triggerMouseEvent(chart, 'mouseout', point);
expect(point._model.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point._model.borderColor).toBe('rgb(50, 100, 150)');
expect(point._model.borderWidth).toBe(2);
expect(point._model.radius).toBe(20);
});
});
});

View File

@ -0,0 +1,395 @@
describe('Chart.controllers.doughnut', function() {
it('should be constructed', function() {
var chart = window.acquireChart({
type: 'doughnut',
data: {
datasets: [{
data: []
}],
labels: []
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.type).toBe('doughnut');
expect(meta.controller).not.toBe(undefined);
expect(meta.controller.index).toBe(0);
expect(meta.data).toEqual([]);
meta.controller.updateIndex(1);
expect(meta.controller.index).toBe(1);
});
it('should create arc elements for each data item during initialization', function() {
var chart = window.acquireChart({
type: 'doughnut',
data: {
datasets: [{
data: [10, 15, 0, 4]
}],
labels: []
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.data.length).toBe(4); // 4 rectangles created
expect(meta.data[0] instanceof Chart.elements.Arc).toBe(true);
expect(meta.data[1] instanceof Chart.elements.Arc).toBe(true);
expect(meta.data[2] instanceof Chart.elements.Arc).toBe(true);
expect(meta.data[3] instanceof Chart.elements.Arc).toBe(true);
});
it('should set the innerRadius to 0 if the config option is 0', function() {
var chart = window.acquireChart({
type: 'pie',
data: {
datasets: [{
data: [10, 15, 0, 4]
}],
labels: []
}
});
expect(chart.innerRadius).toBe(0);
});
it ('should reset and update elements', function() {
var chart = window.acquireChart({
type: 'doughnut',
data: {
datasets: [{
data: [1, 2, 3, 4],
hidden: true
}, {
data: [5, 6, 0, 7]
}, {
data: [8, 9, 10, 11]
}],
labels: ['label0', 'label1', 'label2', 'label3']
},
options: {
legend: false,
title: false,
animation: {
animateRotate: true,
animateScale: false
},
cutoutPercentage: 50,
rotation: Math.PI * -0.5,
circumference: Math.PI * 2.0,
elements: {
arc: {
backgroundColor: 'rgb(255, 0, 0)',
borderColor: 'rgb(0, 0, 255)',
borderWidth: 2
}
}
}
});
var meta = chart.getDatasetMeta(1);
meta.controller.reset(); // reset first
expect(meta.data.length).toBe(4);
[
{c: 0},
{c: 0},
{c: 0},
{c: 0}
].forEach(function(expected, i) {
expect(meta.data[i]._model.x).toBeCloseToPixel(256);
expect(meta.data[i]._model.y).toBeCloseToPixel(256);
expect(meta.data[i]._model.outerRadius).toBeCloseToPixel(254);
expect(meta.data[i]._model.innerRadius).toBeCloseToPixel(190);
expect(meta.data[i]._model.circumference).toBeCloseTo(expected.c, 8);
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
startAngle: Math.PI * -0.5,
endAngle: Math.PI * -0.5,
label: chart.data.labels[i],
backgroundColor: 'rgb(255, 0, 0)',
borderColor: 'rgb(0, 0, 255)',
borderWidth: 2
}));
});
chart.update();
[
{c: 1.7453292519, s: -1.5707963267, e: 0.1745329251},
{c: 2.0943951023, s: 0.1745329251, e: 2.2689280275},
{c: 0, s: 2.2689280275, e: 2.2689280275},
{c: 2.4434609527, s: 2.2689280275, e: 4.7123889803}
].forEach(function(expected, i) {
expect(meta.data[i]._model.x).toBeCloseToPixel(256);
expect(meta.data[i]._model.y).toBeCloseToPixel(256);
expect(meta.data[i]._model.outerRadius).toBeCloseToPixel(254);
expect(meta.data[i]._model.innerRadius).toBeCloseToPixel(190);
expect(meta.data[i]._model.circumference).toBeCloseTo(expected.c, 8);
expect(meta.data[i]._model.startAngle).toBeCloseTo(expected.s, 8);
expect(meta.data[i]._model.endAngle).toBeCloseTo(expected.e, 8);
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
label: chart.data.labels[i],
backgroundColor: 'rgb(255, 0, 0)',
borderColor: 'rgb(0, 0, 255)',
borderWidth: 2
}));
});
// Change the amount of data and ensure that arcs are updated accordingly
chart.data.datasets[1].data = [1, 2]; // remove 2 elements from dataset 0
chart.update();
expect(meta.data.length).toBe(2);
expect(meta.data[0] instanceof Chart.elements.Arc).toBe(true);
expect(meta.data[1] instanceof Chart.elements.Arc).toBe(true);
// Add data
chart.data.datasets[1].data = [1, 2, 3, 4];
chart.update();
expect(meta.data.length).toBe(4);
expect(meta.data[0] instanceof Chart.elements.Arc).toBe(true);
expect(meta.data[1] instanceof Chart.elements.Arc).toBe(true);
expect(meta.data[2] instanceof Chart.elements.Arc).toBe(true);
expect(meta.data[3] instanceof Chart.elements.Arc).toBe(true);
});
it ('should rotate and limit circumference', function() {
var chart = window.acquireChart({
type: 'doughnut',
data: {
datasets: [{
data: [2, 4],
hidden: true
}, {
data: [1, 3]
}, {
data: [1, 0]
}],
labels: ['label0', 'label1']
},
options: {
legend: false,
title: false,
cutoutPercentage: 50,
rotation: Math.PI,
circumference: Math.PI * 0.5,
elements: {
arc: {
backgroundColor: 'rgb(255, 0, 0)',
borderColor: 'rgb(0, 0, 255)',
borderWidth: 2
}
}
}
});
var meta = chart.getDatasetMeta(1);
expect(meta.data.length).toBe(2);
// Only startAngle, endAngle and circumference should be different.
[
{c: Math.PI / 8, s: Math.PI, e: Math.PI + Math.PI / 8},
{c: 3 * Math.PI / 8, s: Math.PI + Math.PI / 8, e: Math.PI + Math.PI / 2}
].forEach(function(expected, i) {
expect(meta.data[i]._model.x).toBeCloseToPixel(510);
expect(meta.data[i]._model.y).toBeCloseToPixel(510);
expect(meta.data[i]._model.outerRadius).toBeCloseToPixel(509);
expect(meta.data[i]._model.innerRadius).toBeCloseToPixel(381);
expect(meta.data[i]._model.circumference).toBeCloseTo(expected.c, 8);
expect(meta.data[i]._model.startAngle).toBeCloseTo(expected.s, 8);
expect(meta.data[i]._model.endAngle).toBeCloseTo(expected.e, 8);
});
});
it('should treat negative values as positive', function() {
var chart = window.acquireChart({
type: 'doughnut',
data: {
datasets: [{
data: [-1, -3]
}],
labels: ['label0', 'label1']
},
options: {
legend: false,
title: false,
cutoutPercentage: 50,
rotation: Math.PI,
circumference: Math.PI * 0.5,
elements: {
arc: {
backgroundColor: 'rgb(255, 0, 0)',
borderColor: 'rgb(0, 0, 255)',
borderWidth: 2
}
}
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.data.length).toBe(2);
// Only startAngle, endAngle and circumference should be different.
[
{c: Math.PI / 8, s: Math.PI, e: Math.PI + Math.PI / 8},
{c: 3 * Math.PI / 8, s: Math.PI + Math.PI / 8, e: Math.PI + Math.PI / 2}
].forEach(function(expected, i) {
expect(meta.data[i]._model.circumference).toBeCloseTo(expected.c, 8);
expect(meta.data[i]._model.startAngle).toBeCloseTo(expected.s, 8);
expect(meta.data[i]._model.endAngle).toBeCloseTo(expected.e, 8);
});
});
it ('should draw all arcs', function() {
var chart = window.acquireChart({
type: 'doughnut',
data: {
datasets: [{
data: [10, 15, 0, 4]
}],
labels: ['label0', 'label1', 'label2', 'label3']
}
});
var meta = chart.getDatasetMeta(0);
spyOn(meta.data[0], 'draw');
spyOn(meta.data[1], 'draw');
spyOn(meta.data[2], 'draw');
spyOn(meta.data[3], 'draw');
chart.update();
expect(meta.data[0].draw.calls.count()).toBe(1);
expect(meta.data[1].draw.calls.count()).toBe(1);
expect(meta.data[2].draw.calls.count()).toBe(1);
expect(meta.data[3].draw.calls.count()).toBe(1);
});
it ('should set the hover style of an arc', function() {
var chart = window.acquireChart({
type: 'doughnut',
data: {
datasets: [{
data: [10, 15, 0, 4]
}],
labels: ['label0', 'label1', 'label2', 'label3']
},
options: {
elements: {
arc: {
backgroundColor: 'rgb(255, 0, 0)',
borderColor: 'rgb(0, 0, 255)',
borderWidth: 2,
}
}
}
});
var meta = chart.getDatasetMeta(0);
var arc = meta.data[0];
meta.controller.setHoverStyle(arc);
expect(arc._model.backgroundColor).toBe('rgb(230, 0, 0)');
expect(arc._model.borderColor).toBe('rgb(0, 0, 230)');
expect(arc._model.borderWidth).toBe(2);
// Set a dataset style to take precedence
chart.data.datasets[0].hoverBackgroundColor = 'rgb(9, 9, 9)';
chart.data.datasets[0].hoverBorderColor = 'rgb(18, 18, 18)';
chart.data.datasets[0].hoverBorderWidth = 1.56;
meta.controller.setHoverStyle(arc);
expect(arc._model.backgroundColor).toBe('rgb(9, 9, 9)');
expect(arc._model.borderColor).toBe('rgb(18, 18, 18)');
expect(arc._model.borderWidth).toBe(1.56);
// Dataset styles can be an array
chart.data.datasets[0].hoverBackgroundColor = ['rgb(255, 255, 255)', 'rgb(9, 9, 9)'];
chart.data.datasets[0].hoverBorderColor = ['rgb(18, 18, 18)'];
chart.data.datasets[0].hoverBorderWidth = [0.1, 1.56];
meta.controller.setHoverStyle(arc);
expect(arc._model.backgroundColor).toBe('rgb(255, 255, 255)');
expect(arc._model.borderColor).toBe('rgb(18, 18, 18)');
expect(arc._model.borderWidth).toBe(0.1);
// Element custom styles also work
arc.custom = {
hoverBackgroundColor: 'rgb(7, 7, 7)',
hoverBorderColor: 'rgb(17, 17, 17)',
hoverBorderWidth: 3.14159,
};
meta.controller.setHoverStyle(arc);
expect(arc._model.backgroundColor).toBe('rgb(7, 7, 7)');
expect(arc._model.borderColor).toBe('rgb(17, 17, 17)');
expect(arc._model.borderWidth).toBe(3.14159);
});
it ('should unset the hover style of an arc', function() {
var chart = window.acquireChart({
type: 'doughnut',
data: {
datasets: [{
data: [10, 15, 0, 4]
}],
labels: ['label0', 'label1', 'label2', 'label3']
},
options: {
elements: {
arc: {
backgroundColor: 'rgb(255, 0, 0)',
borderColor: 'rgb(0, 0, 255)',
borderWidth: 2,
}
}
}
});
var meta = chart.getDatasetMeta(0);
var arc = meta.data[0];
meta.controller.removeHoverStyle(arc);
expect(arc._model.backgroundColor).toBe('rgb(255, 0, 0)');
expect(arc._model.borderColor).toBe('rgb(0, 0, 255)');
expect(arc._model.borderWidth).toBe(2);
// Set a dataset style to take precedence
chart.data.datasets[0].backgroundColor = 'rgb(9, 9, 9)';
chart.data.datasets[0].borderColor = 'rgb(18, 18, 18)';
chart.data.datasets[0].borderWidth = 1.56;
meta.controller.removeHoverStyle(arc);
expect(arc._model.backgroundColor).toBe('rgb(9, 9, 9)');
expect(arc._model.borderColor).toBe('rgb(18, 18, 18)');
expect(arc._model.borderWidth).toBe(1.56);
// Dataset styles can be an array
chart.data.datasets[0].backgroundColor = ['rgb(255, 255, 255)', 'rgb(9, 9, 9)'];
chart.data.datasets[0].borderColor = ['rgb(18, 18, 18)'];
chart.data.datasets[0].borderWidth = [0.1, 1.56];
meta.controller.removeHoverStyle(arc);
expect(arc._model.backgroundColor).toBe('rgb(255, 255, 255)');
expect(arc._model.borderColor).toBe('rgb(18, 18, 18)');
expect(arc._model.borderWidth).toBe(0.1);
// Element custom styles also work
arc.custom = {
backgroundColor: 'rgb(7, 7, 7)',
borderColor: 'rgb(17, 17, 17)',
borderWidth: 3.14159,
};
meta.controller.removeHoverStyle(arc);
expect(arc._model.backgroundColor).toBe('rgb(7, 7, 7)');
expect(arc._model.borderColor).toBe('rgb(17, 17, 17)');
expect(arc._model.borderWidth).toBe(3.14159);
});
});

View File

@ -0,0 +1,787 @@
describe('Chart.controllers.line', function() {
it('should be constructed', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: []
}],
labels: []
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.type).toBe('line');
expect(meta.controller).not.toBe(undefined);
expect(meta.controller.index).toBe(0);
expect(meta.data).toEqual([]);
meta.controller.updateIndex(1);
expect(meta.controller.index).toBe(1);
});
it('Should use the first scale IDs if the dataset does not specify them', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: []
}],
labels: []
},
options: {
scales: {
xAxes: [{
id: 'firstXScaleID'
}],
yAxes: [{
id: 'firstYScaleID'
}]
}
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.xAxisID).toBe('firstXScaleID');
expect(meta.yAxisID).toBe('firstYScaleID');
});
it('Should create line elements and point elements for each data item during initialization', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: [10, 15, 0, -4],
label: 'dataset1'
}],
labels: ['label1', 'label2', 'label3', 'label4']
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.data.length).toBe(4); // 4 points created
expect(meta.data[0] instanceof Chart.elements.Point).toBe(true);
expect(meta.data[1] instanceof Chart.elements.Point).toBe(true);
expect(meta.data[2] instanceof Chart.elements.Point).toBe(true);
expect(meta.data[3] instanceof Chart.elements.Point).toBe(true);
expect(meta.dataset instanceof Chart.elements.Line).toBe(true); // 1 line element
});
it('should draw all elements', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: [10, 15, 0, -4],
label: 'dataset1'
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
showLines: true
}
});
var meta = chart.getDatasetMeta(0);
spyOn(meta.dataset, 'draw');
spyOn(meta.data[0], 'draw');
spyOn(meta.data[1], 'draw');
spyOn(meta.data[2], 'draw');
spyOn(meta.data[3], 'draw');
chart.update();
expect(meta.data[0].draw.calls.count()).toBe(1);
expect(meta.data[1].draw.calls.count()).toBe(1);
expect(meta.data[2].draw.calls.count()).toBe(1);
expect(meta.data[3].draw.calls.count()).toBe(1);
});
it('should draw all elements except lines', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: [10, 15, 0, -4],
label: 'dataset1'
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
showLines: false
}
});
var meta = chart.getDatasetMeta(0);
spyOn(meta.dataset, 'draw');
spyOn(meta.data[0], 'draw');
spyOn(meta.data[1], 'draw');
spyOn(meta.data[2], 'draw');
spyOn(meta.data[3], 'draw');
chart.update();
expect(meta.dataset.draw.calls.count()).toBe(0);
expect(meta.data[0].draw.calls.count()).toBe(1);
expect(meta.data[1].draw.calls.count()).toBe(1);
expect(meta.data[2].draw.calls.count()).toBe(1);
expect(meta.data[3].draw.calls.count()).toBe(1);
});
it('should draw all elements except lines turned off per dataset', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: [10, 15, 0, -4],
label: 'dataset1',
showLine: false
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
showLines: true
}
});
var meta = chart.getDatasetMeta(0);
spyOn(meta.dataset, 'draw');
spyOn(meta.data[0], 'draw');
spyOn(meta.data[1], 'draw');
spyOn(meta.data[2], 'draw');
spyOn(meta.data[3], 'draw');
chart.update();
expect(meta.dataset.draw.calls.count()).toBe(0);
expect(meta.data[0].draw.calls.count()).toBe(1);
expect(meta.data[1].draw.calls.count()).toBe(1);
expect(meta.data[2].draw.calls.count()).toBe(1);
expect(meta.data[3].draw.calls.count()).toBe(1);
});
it('should update elements when modifying data', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: [10, 15, 0, -4],
label: 'dataset',
xAxisID: 'firstXScaleID',
yAxisID: 'firstYScaleID'
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
showLines: true,
legend: false,
title: false,
elements: {
point: {
backgroundColor: 'red',
borderColor: 'blue',
}
},
scales: {
xAxes: [{
id: 'firstXScaleID',
display: false
}],
yAxes: [{
id: 'firstYScaleID',
display: false
}]
}
},
});
var meta = chart.getDatasetMeta(0);
expect(meta.data.length).toBe(4);
chart.data.datasets[0].data = [1, 2]; // remove 2 items
chart.data.datasets[0].borderWidth = 1;
chart.update();
expect(meta.data.length).toBe(2);
[
{x: 0, y: 512},
{x: 171, y: 0}
].forEach(function(expected, i) {
expect(meta.data[i]._datasetIndex).toBe(0);
expect(meta.data[i]._index).toBe(i);
expect(meta.data[i]._xScale).toBe(chart.scales.firstXScaleID);
expect(meta.data[i]._yScale).toBe(chart.scales.firstYScaleID);
expect(meta.data[i]._model.x).toBeCloseToPixel(expected.x);
expect(meta.data[i]._model.y).toBeCloseToPixel(expected.y);
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
backgroundColor: 'red',
borderColor: 'blue',
}));
});
chart.data.datasets[0].data = [1, 2, 3]; // add 1 items
chart.update();
expect(meta.data.length).toBe(3); // should add a new meta data item
});
it('should correctly calculate x scale for label and point', function() {
var chart = window.acquireChart({
type: 'line',
data: {
labels: ['One'],
datasets: [{
data: [1],
}]
},
options: {
legend: false,
title: false,
hover: {
mode: 'single'
},
scales: {
xAxes: [{
display: false,
}],
yAxes: [{
display: false,
ticks: {
beginAtZero: true
}
}]
}
}
});
var meta = chart.getDatasetMeta(0);
// 1 point
var point = meta.data[0];
expect(point._model.x).toBeCloseToPixel(0);
// 2 points
chart.data.labels = ['One', 'Two'];
chart.data.datasets[0].data = [1, 2];
chart.update();
var points = meta.data;
expect(points[0]._model.x).toBeCloseToPixel(0);
expect(points[1]._model.x).toBeCloseToPixel(512);
// 3 points
chart.data.labels = ['One', 'Two', 'Three'];
chart.data.datasets[0].data = [1, 2, 3];
chart.update();
points = meta.data;
expect(points[0]._model.x).toBeCloseToPixel(0);
expect(points[1]._model.x).toBeCloseToPixel(256);
expect(points[2]._model.x).toBeCloseToPixel(512);
// 4 points
chart.data.labels = ['One', 'Two', 'Three', 'Four'];
chart.data.datasets[0].data = [1, 2, 3, 4];
chart.update();
points = meta.data;
expect(points[0]._model.x).toBeCloseToPixel(0);
expect(points[1]._model.x).toBeCloseToPixel(171);
expect(points[2]._model.x).toBeCloseToPixel(340);
expect(points[3]._model.x).toBeCloseToPixel(512);
});
it('should update elements when the y scale is stacked', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: [10, -10, 10, -10],
label: 'dataset1'
}, {
data: [10, 15, 0, -4],
label: 'dataset2'
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
legend: false,
title: false,
scales: {
xAxes: [{
display: false,
}],
yAxes: [{
display: false,
stacked: true
}]
}
}
});
var meta0 = chart.getDatasetMeta(0);
[
{x: 0, y: 146},
{x: 171, y: 439},
{x: 341, y: 146},
{x: 512, y: 439}
].forEach(function(values, i) {
expect(meta0.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta0.data[i]._model.y).toBeCloseToPixel(values.y);
});
var meta1 = chart.getDatasetMeta(1);
[
{x: 0, y: 0},
{x: 171, y: 73},
{x: 341, y: 146},
{x: 512, y: 497}
].forEach(function(values, i) {
expect(meta1.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta1.data[i]._model.y).toBeCloseToPixel(values.y);
});
});
it('should update elements when the y scale is stacked with multiple axes', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: [10, -10, 10, -10],
label: 'dataset1'
}, {
data: [10, 15, 0, -4],
label: 'dataset2'
}, {
data: [10, 10, -10, -10],
label: 'dataset3',
yAxisID: 'secondAxis'
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
legend: false,
title: false,
scales: {
xAxes: [{
display: false,
}],
yAxes: [{
display: false,
stacked: true
}, {
id: 'secondAxis',
type: 'linear',
display: false
}]
}
}
});
var meta0 = chart.getDatasetMeta(0);
[
{x: 0, y: 146},
{x: 171, y: 439},
{x: 341, y: 146},
{x: 512, y: 439}
].forEach(function(values, i) {
expect(meta0.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta0.data[i]._model.y).toBeCloseToPixel(values.y);
});
var meta1 = chart.getDatasetMeta(1);
[
{x: 0, y: 0},
{x: 171, y: 73},
{x: 341, y: 146},
{x: 512, y: 497}
].forEach(function(values, i) {
expect(meta1.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta1.data[i]._model.y).toBeCloseToPixel(values.y);
});
});
it('should update elements when the y scale is stacked and datasets is scatter data', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: [{
x: 0,
y: 10
}, {
x: 1,
y: -10
}, {
x: 2,
y: 10
}, {
x: 3,
y: -10
}],
label: 'dataset1'
}, {
data: [{
x: 0,
y: 10
}, {
x: 1,
y: 15
}, {
x: 2,
y: 0
}, {
x: 3,
y: -4
}],
label: 'dataset2'
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
legend: false,
title: false,
scales: {
xAxes: [{
display: false,
}],
yAxes: [{
display: false,
stacked: true
}]
}
}
});
var meta0 = chart.getDatasetMeta(0);
[
{x: 0, y: 146},
{x: 171, y: 439},
{x: 341, y: 146},
{x: 512, y: 439}
].forEach(function(values, i) {
expect(meta0.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta0.data[i]._model.y).toBeCloseToPixel(values.y);
});
var meta1 = chart.getDatasetMeta(1);
[
{x: 0, y: 0},
{x: 171, y: 73},
{x: 341, y: 146},
{x: 512, y: 497}
].forEach(function(values, i) {
expect(meta1.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta1.data[i]._model.y).toBeCloseToPixel(values.y);
});
});
it('should update elements when the y scale is stacked and data is strings', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: ['10', '-10', '10', '-10'],
label: 'dataset1'
}, {
data: ['10', '15', '0', '-4'],
label: 'dataset2'
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
legend: false,
title: false,
scales: {
xAxes: [{
display: false,
}],
yAxes: [{
display: false,
stacked: true
}]
}
}
});
var meta0 = chart.getDatasetMeta(0);
[
{x: 0, y: 146},
{x: 171, y: 439},
{x: 341, y: 146},
{x: 512, y: 439}
].forEach(function(values, i) {
expect(meta0.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta0.data[i]._model.y).toBeCloseToPixel(values.y);
});
var meta1 = chart.getDatasetMeta(1);
[
{x: 0, y: 0},
{x: 171, y: 73},
{x: 341, y: 146},
{x: 512, y: 497}
].forEach(function(values, i) {
expect(meta1.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta1.data[i]._model.y).toBeCloseToPixel(values.y);
});
});
it('should fall back to the line styles for points', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: [0, 0],
label: 'dataset1',
// line styles
backgroundColor: 'rgb(98, 98, 98)',
borderColor: 'rgb(8, 8, 8)',
borderWidth: 0.55,
}],
labels: ['label1', 'label2']
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.dataset._model.backgroundColor).toBe('rgb(98, 98, 98)');
expect(meta.dataset._model.borderColor).toBe('rgb(8, 8, 8)');
expect(meta.dataset._model.borderWidth).toBe(0.55);
});
it('should handle number of data point changes in update', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: [10, 15, 0, -4],
label: 'dataset1',
}],
labels: ['label1', 'label2', 'label3', 'label4']
}
});
var meta = chart.getDatasetMeta(0);
chart.data.datasets[0].data = [1, 2]; // remove 2 items
chart.update();
expect(meta.data.length).toBe(2);
expect(meta.data[0] instanceof Chart.elements.Point).toBe(true);
expect(meta.data[1] instanceof Chart.elements.Point).toBe(true);
chart.data.datasets[0].data = [1, 2, 3, 4, 5]; // add 3 items
chart.update();
expect(meta.data.length).toBe(5);
expect(meta.data[0] instanceof Chart.elements.Point).toBe(true);
expect(meta.data[1] instanceof Chart.elements.Point).toBe(true);
expect(meta.data[2] instanceof Chart.elements.Point).toBe(true);
expect(meta.data[3] instanceof Chart.elements.Point).toBe(true);
expect(meta.data[4] instanceof Chart.elements.Point).toBe(true);
});
it('should set point hover styles', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: [10, 15, 0, -4],
label: 'dataset1',
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
elements: {
point: {
backgroundColor: 'rgb(255, 255, 0)',
borderWidth: 1,
borderColor: 'rgb(255, 255, 255)',
hitRadius: 1,
hoverRadius: 4,
hoverBorderWidth: 1,
radius: 3,
}
}
}
});
var meta = chart.getDatasetMeta(0);
var point = meta.data[0];
meta.controller.setHoverStyle(point);
expect(point._model.backgroundColor).toBe('rgb(229, 230, 0)');
expect(point._model.borderColor).toBe('rgb(230, 230, 230)');
expect(point._model.borderWidth).toBe(1);
expect(point._model.radius).toBe(4);
// Can set hover style per dataset
chart.data.datasets[0].pointHoverRadius = 3.3;
chart.data.datasets[0].pointHoverBackgroundColor = 'rgb(77, 79, 81)';
chart.data.datasets[0].pointHoverBorderColor = 'rgb(123, 125, 127)';
chart.data.datasets[0].pointHoverBorderWidth = 2.1;
meta.controller.setHoverStyle(point);
expect(point._model.backgroundColor).toBe('rgb(77, 79, 81)');
expect(point._model.borderColor).toBe('rgb(123, 125, 127)');
expect(point._model.borderWidth).toBe(2.1);
expect(point._model.radius).toBe(3.3);
// Use the consistent name "pointRadius", setting but overwriting
// another value in "radius"
chart.data.datasets[0].pointRadius = 250;
chart.data.datasets[0].radius = 20;
meta.controller.setHoverStyle(point);
expect(point._model.backgroundColor).toBe('rgb(77, 79, 81)');
expect(point._model.borderColor).toBe('rgb(123, 125, 127)');
expect(point._model.borderWidth).toBe(2.1);
expect(point._model.radius).toBe(3.3);
// Custom style
point.custom = {
hoverRadius: 4.4,
hoverBorderWidth: 5.5,
hoverBackgroundColor: 'rgb(0, 0, 0)',
hoverBorderColor: 'rgb(10, 10, 10)'
};
meta.controller.setHoverStyle(point);
expect(point._model.backgroundColor).toBe('rgb(0, 0, 0)');
expect(point._model.borderColor).toBe('rgb(10, 10, 10)');
expect(point._model.borderWidth).toBe(5.5);
expect(point._model.radius).toBe(4.4);
});
it('should remove hover styles', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: [10, 15, 0, -4],
label: 'dataset1',
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
elements: {
point: {
backgroundColor: 'rgb(255, 255, 0)',
borderWidth: 1,
borderColor: 'rgb(255, 255, 255)',
hitRadius: 1,
hoverRadius: 4,
hoverBorderWidth: 1,
radius: 3,
}
}
}
});
var meta = chart.getDatasetMeta(0);
var point = meta.data[0];
chart.options.elements.point.backgroundColor = 'rgb(45, 46, 47)';
chart.options.elements.point.borderColor = 'rgb(50, 51, 52)';
chart.options.elements.point.borderWidth = 10.1;
chart.options.elements.point.radius = 1.01;
meta.controller.removeHoverStyle(point);
expect(point._model.backgroundColor).toBe('rgb(45, 46, 47)');
expect(point._model.borderColor).toBe('rgb(50, 51, 52)');
expect(point._model.borderWidth).toBe(10.1);
expect(point._model.radius).toBe(1.01);
// Can set hover style per dataset
chart.data.datasets[0].radius = 3.3;
chart.data.datasets[0].pointBackgroundColor = 'rgb(77, 79, 81)';
chart.data.datasets[0].pointBorderColor = 'rgb(123, 125, 127)';
chart.data.datasets[0].pointBorderWidth = 2.1;
meta.controller.removeHoverStyle(point);
expect(point._model.backgroundColor).toBe('rgb(77, 79, 81)');
expect(point._model.borderColor).toBe('rgb(123, 125, 127)');
expect(point._model.borderWidth).toBe(2.1);
expect(point._model.radius).toBe(3.3);
// Use the consistent name "pointRadius", setting but overwriting
// another value in "radius"
chart.data.datasets[0].pointRadius = 250;
chart.data.datasets[0].radius = 20;
meta.controller.removeHoverStyle(point);
expect(point._model.backgroundColor).toBe('rgb(77, 79, 81)');
expect(point._model.borderColor).toBe('rgb(123, 125, 127)');
expect(point._model.borderWidth).toBe(2.1);
expect(point._model.radius).toBe(250);
// Custom style
point.custom = {
radius: 4.4,
borderWidth: 5.5,
backgroundColor: 'rgb(0, 0, 0)',
borderColor: 'rgb(10, 10, 10)'
};
meta.controller.removeHoverStyle(point);
expect(point._model.backgroundColor).toBe('rgb(0, 0, 0)');
expect(point._model.borderColor).toBe('rgb(10, 10, 10)');
expect(point._model.borderWidth).toBe(5.5);
expect(point._model.radius).toBe(4.4);
});
it('should allow 0 as a point border width', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: [10, 15, 0, -4],
label: 'dataset1',
pointBorderWidth: 0
}],
labels: ['label1', 'label2', 'label3', 'label4']
}
});
var meta = chart.getDatasetMeta(0);
var point = meta.data[0];
expect(point._model.borderWidth).toBe(0);
});
it('should allow an array as the point border width setting', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: [10, 15, 0, -4],
label: 'dataset1',
pointBorderWidth: [1, 2, 3, 4]
}],
labels: ['label1', 'label2', 'label3', 'label4']
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.data[0]._model.borderWidth).toBe(1);
expect(meta.data[1]._model.borderWidth).toBe(2);
expect(meta.data[2]._model.borderWidth).toBe(3);
expect(meta.data[3]._model.borderWidth).toBe(4);
});
});

View File

@ -0,0 +1,360 @@
describe('Chart.controllers.polarArea', function() {
it('should be constructed', function() {
var chart = window.acquireChart({
type: 'polarArea',
data: {
datasets: [
{data: []},
{data: []}
],
labels: []
}
});
var meta = chart.getDatasetMeta(1);
expect(meta.type).toEqual('polarArea');
expect(meta.data).toEqual([]);
expect(meta.hidden).toBe(null);
expect(meta.controller).not.toBe(undefined);
expect(meta.controller.index).toBe(1);
meta.controller.updateIndex(0);
expect(meta.controller.index).toBe(0);
});
it('should create arc elements for each data item during initialization', function() {
var chart = window.acquireChart({
type: 'polarArea',
data: {
datasets: [
{data: []},
{data: [10, 15, 0, -4]}
],
labels: []
}
});
var meta = chart.getDatasetMeta(1);
expect(meta.data.length).toBe(4); // 4 arcs created
expect(meta.data[0] instanceof Chart.elements.Arc).toBe(true);
expect(meta.data[1] instanceof Chart.elements.Arc).toBe(true);
expect(meta.data[2] instanceof Chart.elements.Arc).toBe(true);
expect(meta.data[3] instanceof Chart.elements.Arc).toBe(true);
});
it('should draw all elements', function() {
var chart = window.acquireChart({
type: 'polarArea',
data: {
datasets: [{
data: [10, 15, 0, -4],
label: 'dataset2'
}],
labels: ['label1', 'label2', 'label3', 'label4']
}
});
var meta = chart.getDatasetMeta(0);
spyOn(meta.data[0], 'draw');
spyOn(meta.data[1], 'draw');
spyOn(meta.data[2], 'draw');
spyOn(meta.data[3], 'draw');
chart.update();
expect(meta.data[0].draw.calls.count()).toBe(1);
expect(meta.data[1].draw.calls.count()).toBe(1);
expect(meta.data[2].draw.calls.count()).toBe(1);
expect(meta.data[3].draw.calls.count()).toBe(1);
});
it('should update elements when modifying data', function() {
var chart = window.acquireChart({
type: 'polarArea',
data: {
datasets: [{
data: [10, 15, 0, -4],
label: 'dataset2'
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
showLines: true,
legend: false,
title: false,
elements: {
arc: {
backgroundColor: 'rgb(255, 0, 0)',
borderColor: 'rgb(0, 255, 0)',
borderWidth: 1.2
}
}
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.data.length).toBe(4);
[
{o: 179, s: -0.5 * Math.PI, e: 0},
{o: 243, s: 0, e: 0.5 * Math.PI},
{o: 51, s: 0.5 * Math.PI, e: Math.PI},
{o: 0, s: Math.PI, e: 1.5 * Math.PI}
].forEach(function(expected, i) {
expect(meta.data[i]._model.x).toBeCloseToPixel(256);
expect(meta.data[i]._model.y).toBeCloseToPixel(256);
expect(meta.data[i]._model.innerRadius).toBeCloseToPixel(0);
expect(meta.data[i]._model.outerRadius).toBeCloseToPixel(expected.o);
expect(meta.data[i]._model.startAngle).toBe(expected.s);
expect(meta.data[i]._model.endAngle).toBe(expected.e);
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
backgroundColor: 'rgb(255, 0, 0)',
borderColor: 'rgb(0, 255, 0)',
borderWidth: 1.2,
label: chart.data.labels[i]
}));
});
// arc styles
chart.data.datasets[0].backgroundColor = 'rgb(128, 129, 130)';
chart.data.datasets[0].borderColor = 'rgb(56, 57, 58)';
chart.data.datasets[0].borderWidth = 1.123;
chart.update();
for (var i = 0; i < 4; ++i) {
expect(meta.data[i]._model.backgroundColor).toBe('rgb(128, 129, 130)');
expect(meta.data[i]._model.borderColor).toBe('rgb(56, 57, 58)');
expect(meta.data[i]._model.borderWidth).toBe(1.123);
}
// arc styles
meta.data[0].custom = {
backgroundColor: 'rgb(0, 1, 3)',
borderColor: 'rgb(4, 6, 8)',
borderWidth: 0.787
};
chart.update();
expect(meta.data[0]._model.x).toBeCloseToPixel(256);
expect(meta.data[0]._model.y).toBeCloseToPixel(256);
expect(meta.data[0]._model.innerRadius).toBeCloseToPixel(0);
expect(meta.data[0]._model.outerRadius).toBeCloseToPixel(179);
expect(meta.data[0]._model).toEqual(jasmine.objectContaining({
startAngle: -0.5 * Math.PI,
endAngle: 0,
backgroundColor: 'rgb(0, 1, 3)',
borderWidth: 0.787,
borderColor: 'rgb(4, 6, 8)',
label: 'label1'
}));
});
it('should update elements with start angle from options', function() {
var chart = window.acquireChart({
type: 'polarArea',
data: {
datasets: [{
data: [10, 15, 0, -4],
label: 'dataset2'
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
showLines: true,
legend: false,
title: false,
startAngle: 0, // default is -0.5 * Math.PI
elements: {
arc: {
backgroundColor: 'rgb(255, 0, 0)',
borderColor: 'rgb(0, 255, 0)',
borderWidth: 1.2
}
}
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.data.length).toBe(4);
[
{o: 179, s: 0, e: 0.5 * Math.PI},
{o: 243, s: 0.5 * Math.PI, e: Math.PI},
{o: 51, s: Math.PI, e: 1.5 * Math.PI},
{o: 0, s: 1.5 * Math.PI, e: 2.0 * Math.PI}
].forEach(function(expected, i) {
expect(meta.data[i]._model.x).toBeCloseToPixel(256);
expect(meta.data[i]._model.y).toBeCloseToPixel(256);
expect(meta.data[i]._model.innerRadius).toBeCloseToPixel(0);
expect(meta.data[i]._model.outerRadius).toBeCloseToPixel(expected.o);
expect(meta.data[i]._model.startAngle).toBe(expected.s);
expect(meta.data[i]._model.endAngle).toBe(expected.e);
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
backgroundColor: 'rgb(255, 0, 0)',
borderColor: 'rgb(0, 255, 0)',
borderWidth: 1.2,
label: chart.data.labels[i]
}));
});
});
it('should handle number of data point changes in update', function() {
var chart = window.acquireChart({
type: 'polarArea',
data: {
datasets: [{
data: [10, 15, 0, -4],
label: 'dataset2'
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
showLines: true,
elements: {
arc: {
backgroundColor: 'rgb(255, 0, 0)',
borderColor: 'rgb(0, 255, 0)',
borderWidth: 1.2
}
}
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.data.length).toBe(4);
// remove 2 items
chart.data.labels = ['label1', 'label2'];
chart.data.datasets[0].data = [1, 2];
chart.update();
expect(meta.data.length).toBe(2);
expect(meta.data[0] instanceof Chart.elements.Arc).toBe(true);
expect(meta.data[1] instanceof Chart.elements.Arc).toBe(true);
// add 3 items
chart.data.labels = ['label1', 'label2', 'label3', 'label4', 'label5'];
chart.data.datasets[0].data = [1, 2, 3, 4, 5];
chart.update();
expect(meta.data.length).toBe(5);
expect(meta.data[0] instanceof Chart.elements.Arc).toBe(true);
expect(meta.data[1] instanceof Chart.elements.Arc).toBe(true);
expect(meta.data[2] instanceof Chart.elements.Arc).toBe(true);
expect(meta.data[3] instanceof Chart.elements.Arc).toBe(true);
expect(meta.data[4] instanceof Chart.elements.Arc).toBe(true);
});
it('should set arc hover styles', function() {
var chart = window.acquireChart({
type: 'polarArea',
data: {
datasets: [{
data: [10, 15, 0, -4],
label: 'dataset2'
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
showLines: true,
elements: {
arc: {
backgroundColor: 'rgb(255, 0, 0)',
borderColor: 'rgb(0, 255, 0)',
borderWidth: 1.2
}
}
}
});
var meta = chart.getDatasetMeta(0);
var arc = meta.data[0];
meta.controller.setHoverStyle(arc);
expect(arc._model.backgroundColor).toBe('rgb(230, 0, 0)');
expect(arc._model.borderColor).toBe('rgb(0, 230, 0)');
expect(arc._model.borderWidth).toBe(1.2);
// Can set hover style per dataset
chart.data.datasets[0].hoverBackgroundColor = 'rgb(77, 79, 81)';
chart.data.datasets[0].hoverBorderColor = 'rgb(123, 125, 127)';
chart.data.datasets[0].hoverBorderWidth = 2.1;
meta.controller.setHoverStyle(arc);
expect(arc._model.backgroundColor).toBe('rgb(77, 79, 81)');
expect(arc._model.borderColor).toBe('rgb(123, 125, 127)');
expect(arc._model.borderWidth).toBe(2.1);
// Custom style
arc.custom = {
hoverBorderWidth: 5.5,
hoverBackgroundColor: 'rgb(0, 0, 0)',
hoverBorderColor: 'rgb(10, 10, 10)'
};
meta.controller.setHoverStyle(arc);
expect(arc._model.backgroundColor).toBe('rgb(0, 0, 0)');
expect(arc._model.borderColor).toBe('rgb(10, 10, 10)');
expect(arc._model.borderWidth).toBe(5.5);
});
it('should remove hover styles', function() {
var chart = window.acquireChart({
type: 'polarArea',
data: {
datasets: [{
data: [10, 15, 0, -4],
label: 'dataset2'
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
showLines: true,
elements: {
arc: {
backgroundColor: 'rgb(255, 0, 0)',
borderColor: 'rgb(0, 255, 0)',
borderWidth: 1.2
}
}
}
});
var meta = chart.getDatasetMeta(0);
var arc = meta.data[0];
chart.options.elements.arc.backgroundColor = 'rgb(45, 46, 47)';
chart.options.elements.arc.borderColor = 'rgb(50, 51, 52)';
chart.options.elements.arc.borderWidth = 10.1;
meta.controller.removeHoverStyle(arc);
expect(arc._model.backgroundColor).toBe('rgb(45, 46, 47)');
expect(arc._model.borderColor).toBe('rgb(50, 51, 52)');
expect(arc._model.borderWidth).toBe(10.1);
// Can set hover style per dataset
chart.data.datasets[0].backgroundColor = 'rgb(77, 79, 81)';
chart.data.datasets[0].borderColor = 'rgb(123, 125, 127)';
chart.data.datasets[0].borderWidth = 2.1;
meta.controller.removeHoverStyle(arc);
expect(arc._model.backgroundColor).toBe('rgb(77, 79, 81)');
expect(arc._model.borderColor).toBe('rgb(123, 125, 127)');
expect(arc._model.borderWidth).toBe(2.1);
// Custom style
arc.custom = {
borderWidth: 5.5,
backgroundColor: 'rgb(0, 0, 0)',
borderColor: 'rgb(10, 10, 10)'
};
meta.controller.removeHoverStyle(arc);
expect(arc._model.backgroundColor).toBe('rgb(0, 0, 0)');
expect(arc._model.borderColor).toBe('rgb(10, 10, 10)');
expect(arc._model.borderWidth).toBe(5.5);
});
});

View File

@ -0,0 +1,483 @@
describe('Chart.controllers.radar', function() {
it('Should be constructed', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: []
}],
labels: []
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.type).toBe('radar');
expect(meta.controller).not.toBe(undefined);
expect(meta.controller.index).toBe(0);
expect(meta.data).toEqual([]);
meta.controller.updateIndex(1);
expect(meta.controller.index).toBe(1);
});
it('Should create arc elements for each data item during initialization', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [10, 15, 0, 4]
}],
labels: ['label1', 'label2', 'label3', 'label4']
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.dataset instanceof Chart.elements.Line).toBe(true); // line element
expect(meta.data.length).toBe(4); // 4 points created
expect(meta.data[0] instanceof Chart.elements.Point).toBe(true);
expect(meta.data[1] instanceof Chart.elements.Point).toBe(true);
expect(meta.data[2] instanceof Chart.elements.Point).toBe(true);
expect(meta.data[3] instanceof Chart.elements.Point).toBe(true);
});
it('should draw all elements', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [10, 15, 0, 4]
}],
labels: ['label1', 'label2', 'label3', 'label4']
}
});
var meta = chart.getDatasetMeta(0);
spyOn(meta.dataset, 'draw');
spyOn(meta.data[0], 'draw');
spyOn(meta.data[1], 'draw');
spyOn(meta.data[2], 'draw');
spyOn(meta.data[3], 'draw');
chart.update();
expect(meta.dataset.draw.calls.count()).toBe(1);
expect(meta.data[0].draw.calls.count()).toBe(1);
expect(meta.data[1].draw.calls.count()).toBe(1);
expect(meta.data[2].draw.calls.count()).toBe(1);
expect(meta.data[3].draw.calls.count()).toBe(1);
});
it('should update elements', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [10, 15, 0, 4]
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
showLines: true,
legend: false,
title: false,
elements: {
line: {
backgroundColor: 'rgb(255, 0, 0)',
borderCapStyle: 'round',
borderColor: 'rgb(0, 255, 0)',
borderDash: [],
borderDashOffset: 0.1,
borderJoinStyle: 'bevel',
borderWidth: 1.2,
fill: true,
tension: 0.1,
},
point: {
backgroundColor: Chart.defaults.global.defaultColor,
borderWidth: 1,
borderColor: Chart.defaults.global.defaultColor,
hitRadius: 1,
hoverRadius: 4,
hoverBorderWidth: 1,
radius: 3,
pointStyle: 'circle'
}
}
}
});
var meta = chart.getDatasetMeta(0);
meta.controller.reset(); // reset first
// Line element
expect(meta.dataset._model).toEqual(jasmine.objectContaining({
backgroundColor: 'rgb(255, 0, 0)',
borderCapStyle: 'round',
borderColor: 'rgb(0, 255, 0)',
borderDash: [],
borderDashOffset: 0.1,
borderJoinStyle: 'bevel',
borderWidth: 1.2,
fill: true,
tension: 0.1,
}));
[
{x: 256, y: 256, cppx: 256, cppy: 256, cpnx: 256, cpny: 256},
{x: 256, y: 256, cppx: 256, cppy: 256, cpnx: 256, cpny: 256},
{x: 256, y: 256, cppx: 256, cppy: 256, cpnx: 256, cpny: 256},
{x: 256, y: 256, cppx: 256, cppy: 256, cpnx: 256, cpny: 256},
].forEach(function(expected, i) {
expect(meta.data[i]._model.x).toBeCloseToPixel(expected.x);
expect(meta.data[i]._model.y).toBeCloseToPixel(expected.y);
expect(meta.data[i]._model.controlPointPreviousX).toBeCloseToPixel(expected.cppx);
expect(meta.data[i]._model.controlPointPreviousY).toBeCloseToPixel(expected.cppy);
expect(meta.data[i]._model.controlPointNextX).toBeCloseToPixel(expected.cpnx);
expect(meta.data[i]._model.controlPointNextY).toBeCloseToPixel(expected.cpny);
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
backgroundColor: Chart.defaults.global.defaultColor,
borderWidth: 1,
borderColor: Chart.defaults.global.defaultColor,
hitRadius: 1,
radius: 3,
pointStyle: 'circle',
skip: false,
tension: 0.1,
}));
});
// Now update controller and ensure proper updates
meta.controller.update();
[
{x: 256, y: 117, cppx: 246, cppy: 117, cpnx: 272, cpny: 117},
{x: 464, y: 256, cppx: 464, cppy: 248, cpnx: 464, cpny: 262},
{x: 256, y: 256, cppx: 276.9, cppy: 256, cpnx: 250.4, cpny: 256},
{x: 200, y: 256, cppx: 200, cppy: 259, cpnx: 200, cpny: 245},
].forEach(function(expected, i) {
expect(meta.data[i]._model.x).toBeCloseToPixel(expected.x);
expect(meta.data[i]._model.y).toBeCloseToPixel(expected.y);
expect(meta.data[i]._model.controlPointPreviousX).toBeCloseToPixel(expected.cppx);
expect(meta.data[i]._model.controlPointPreviousY).toBeCloseToPixel(expected.cppy);
expect(meta.data[i]._model.controlPointNextX).toBeCloseToPixel(expected.cpnx);
expect(meta.data[i]._model.controlPointNextY).toBeCloseToPixel(expected.cpny);
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
backgroundColor: Chart.defaults.global.defaultColor,
borderWidth: 1,
borderColor: Chart.defaults.global.defaultColor,
hitRadius: 1,
radius: 3,
pointStyle: 'circle',
skip: false,
tension: 0.1,
}));
});
// Use dataset level styles for lines & points
chart.data.datasets[0].lineTension = 0;
chart.data.datasets[0].backgroundColor = 'rgb(98, 98, 98)';
chart.data.datasets[0].borderColor = 'rgb(8, 8, 8)';
chart.data.datasets[0].borderWidth = 0.55;
chart.data.datasets[0].borderCapStyle = 'butt';
chart.data.datasets[0].borderDash = [2, 3];
chart.data.datasets[0].borderDashOffset = 7;
chart.data.datasets[0].borderJoinStyle = 'miter';
chart.data.datasets[0].fill = false;
// point styles
chart.data.datasets[0].pointRadius = 22;
chart.data.datasets[0].hitRadius = 3.3;
chart.data.datasets[0].pointBackgroundColor = 'rgb(128, 129, 130)';
chart.data.datasets[0].pointBorderColor = 'rgb(56, 57, 58)';
chart.data.datasets[0].pointBorderWidth = 1.123;
meta.controller.update();
expect(meta.dataset._model).toEqual(jasmine.objectContaining({
backgroundColor: 'rgb(98, 98, 98)',
borderCapStyle: 'butt',
borderColor: 'rgb(8, 8, 8)',
borderDash: [2, 3],
borderDashOffset: 7,
borderJoinStyle: 'miter',
borderWidth: 0.55,
fill: false,
tension: 0,
}));
// Since tension is now 0, we don't care about the control points
[
{x: 256, y: 117},
{x: 464, y: 256},
{x: 256, y: 256},
{x: 200, y: 256},
].forEach(function(expected, i) {
expect(meta.data[i]._model.x).toBeCloseToPixel(expected.x);
expect(meta.data[i]._model.y).toBeCloseToPixel(expected.y);
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
backgroundColor: 'rgb(128, 129, 130)',
borderWidth: 1.123,
borderColor: 'rgb(56, 57, 58)',
hitRadius: 3.3,
radius: 22,
pointStyle: 'circle',
skip: false,
tension: 0,
}));
});
// Use custom styles for lines & first point
meta.dataset.custom = {
tension: 0.25,
backgroundColor: 'rgb(55, 55, 54)',
borderColor: 'rgb(8, 7, 6)',
borderWidth: 0.3,
borderCapStyle: 'square',
borderDash: [4, 3],
borderDashOffset: 4.4,
borderJoinStyle: 'round',
fill: true,
};
// point styles
meta.data[0].custom = {
radius: 2.2,
backgroundColor: 'rgb(0, 1, 3)',
borderColor: 'rgb(4, 6, 8)',
borderWidth: 0.787,
tension: 0.15,
skip: true,
hitRadius: 5,
};
meta.controller.update();
expect(meta.dataset._model).toEqual(jasmine.objectContaining({
backgroundColor: 'rgb(55, 55, 54)',
borderCapStyle: 'square',
borderColor: 'rgb(8, 7, 6)',
borderDash: [4, 3],
borderDashOffset: 4.4,
borderJoinStyle: 'round',
borderWidth: 0.3,
fill: true,
tension: 0.25,
}));
expect(meta.data[0]._model.x).toBeCloseToPixel(256);
expect(meta.data[0]._model.y).toBeCloseToPixel(117);
expect(meta.data[0]._model.controlPointPreviousX).toBeCloseToPixel(241);
expect(meta.data[0]._model.controlPointPreviousY).toBeCloseToPixel(117);
expect(meta.data[0]._model.controlPointNextX).toBeCloseToPixel(281);
expect(meta.data[0]._model.controlPointNextY).toBeCloseToPixel(117);
expect(meta.data[0]._model).toEqual(jasmine.objectContaining({
radius: 2.2,
backgroundColor: 'rgb(0, 1, 3)',
borderColor: 'rgb(4, 6, 8)',
borderWidth: 0.787,
tension: 0.15,
skip: true,
hitRadius: 5,
}));
});
it('should set point hover styles', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [10, 15, 0, 4]
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
showLines: true,
elements: {
line: {
backgroundColor: 'rgb(255, 0, 0)',
borderCapStyle: 'round',
borderColor: 'rgb(0, 255, 0)',
borderDash: [],
borderDashOffset: 0.1,
borderJoinStyle: 'bevel',
borderWidth: 1.2,
fill: true,
skipNull: true,
tension: 0.1,
},
point: {
backgroundColor: 'rgb(255, 255, 0)',
borderWidth: 1,
borderColor: 'rgb(255, 255, 255)',
hitRadius: 1,
hoverRadius: 4,
hoverBorderWidth: 1,
radius: 3,
}
}
}
});
var meta = chart.getDatasetMeta(0);
meta.controller.update(); // reset first
var point = meta.data[0];
meta.controller.setHoverStyle(point);
expect(point._model.backgroundColor).toBe('rgb(229, 230, 0)');
expect(point._model.borderColor).toBe('rgb(230, 230, 230)');
expect(point._model.borderWidth).toBe(1);
expect(point._model.radius).toBe(4);
// Can set hover style per dataset
chart.data.datasets[0].pointHoverRadius = 3.3;
chart.data.datasets[0].pointHoverBackgroundColor = 'rgb(77, 79, 81)';
chart.data.datasets[0].pointHoverBorderColor = 'rgb(123, 125, 127)';
chart.data.datasets[0].pointHoverBorderWidth = 2.1;
meta.controller.setHoverStyle(point);
expect(point._model.backgroundColor).toBe('rgb(77, 79, 81)');
expect(point._model.borderColor).toBe('rgb(123, 125, 127)');
expect(point._model.borderWidth).toBe(2.1);
expect(point._model.radius).toBe(3.3);
// Custom style
point.custom = {
hoverRadius: 4.4,
hoverBorderWidth: 5.5,
hoverBackgroundColor: 'rgb(0, 0, 0)',
hoverBorderColor: 'rgb(10, 10, 10)'
};
meta.controller.setHoverStyle(point);
expect(point._model.backgroundColor).toBe('rgb(0, 0, 0)');
expect(point._model.borderColor).toBe('rgb(10, 10, 10)');
expect(point._model.borderWidth).toBe(5.5);
expect(point._model.radius).toBe(4.4);
});
it('should remove hover styles', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [10, 15, 0, 4]
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
showLines: true,
elements: {
line: {
backgroundColor: 'rgb(255, 0, 0)',
borderCapStyle: 'round',
borderColor: 'rgb(0, 255, 0)',
borderDash: [],
borderDashOffset: 0.1,
borderJoinStyle: 'bevel',
borderWidth: 1.2,
fill: true,
skipNull: true,
tension: 0.1,
},
point: {
backgroundColor: 'rgb(255, 255, 0)',
borderWidth: 1,
borderColor: 'rgb(255, 255, 255)',
hitRadius: 1,
hoverRadius: 4,
hoverBorderWidth: 1,
radius: 3,
}
}
}
});
var meta = chart.getDatasetMeta(0);
meta.controller.update(); // reset first
var point = meta.data[0];
chart.options.elements.point.backgroundColor = 'rgb(45, 46, 47)';
chart.options.elements.point.borderColor = 'rgb(50, 51, 52)';
chart.options.elements.point.borderWidth = 10.1;
chart.options.elements.point.radius = 1.01;
meta.controller.removeHoverStyle(point);
expect(point._model.backgroundColor).toBe('rgb(45, 46, 47)');
expect(point._model.borderColor).toBe('rgb(50, 51, 52)');
expect(point._model.borderWidth).toBe(10.1);
expect(point._model.radius).toBe(1.01);
// Can set hover style per dataset
chart.data.datasets[0].pointRadius = 3.3;
chart.data.datasets[0].pointBackgroundColor = 'rgb(77, 79, 81)';
chart.data.datasets[0].pointBorderColor = 'rgb(123, 125, 127)';
chart.data.datasets[0].pointBorderWidth = 2.1;
meta.controller.removeHoverStyle(point);
expect(point._model.backgroundColor).toBe('rgb(77, 79, 81)');
expect(point._model.borderColor).toBe('rgb(123, 125, 127)');
expect(point._model.borderWidth).toBe(2.1);
expect(point._model.radius).toBe(3.3);
// Custom style
point.custom = {
radius: 4.4,
borderWidth: 5.5,
backgroundColor: 'rgb(0, 0, 0)',
borderColor: 'rgb(10, 10, 10)'
};
meta.controller.removeHoverStyle(point);
expect(point._model.backgroundColor).toBe('rgb(0, 0, 0)');
expect(point._model.borderColor).toBe('rgb(10, 10, 10)');
expect(point._model.borderWidth).toBe(5.5);
expect(point._model.radius).toBe(4.4);
});
it('should allow pointBorderWidth to be set to 0', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [10, 15, 0, 4],
pointBorderWidth: 0
}],
labels: ['label1', 'label2', 'label3', 'label4']
}
});
var meta = chart.getDatasetMeta(0);
var point = meta.data[0];
expect(point._model.borderWidth).toBe(0);
});
it('should use the pointRadius setting over the radius setting', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [10, 15, 0, 4],
pointRadius: 10,
radius: 15,
}, {
data: [20, 20, 20, 20],
radius: 20
}],
labels: ['label1', 'label2', 'label3', 'label4']
}
});
var meta0 = chart.getDatasetMeta(0);
var meta1 = chart.getDatasetMeta(1);
expect(meta0.data[0]._model.radius).toBe(10);
expect(meta1.data[0]._model.radius).toBe(20);
});
});

View File

@ -0,0 +1,25 @@
describe('Chart.controllers.scatter', function() {
describe('showLines option', function() {
it('should not draw a line if undefined', function() {
var chart = window.acquireChart({
type: 'scatter',
data: {
datasets: [{
data: [{x: 10, y: 15}],
label: 'dataset1'
}],
},
options: {}
});
var meta = chart.getDatasetMeta(0);
spyOn(meta.dataset, 'draw');
spyOn(meta.data[0], 'draw');
chart.update();
expect(meta.dataset.draw.calls.count()).toBe(0);
expect(meta.data[0].draw.calls.count()).toBe(1);
});
});
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,188 @@
describe('Chart.DatasetController', function() {
it('should listen for dataset data insertions or removals', function() {
var data = [0, 1, 2, 3, 4, 5];
var chart = acquireChart({
type: 'line',
data: {
datasets: [{
data: data
}]
}
});
var controller = chart.getDatasetMeta(0).controller;
var methods = [
'onDataPush',
'onDataPop',
'onDataShift',
'onDataSplice',
'onDataUnshift'
];
methods.forEach(function(method) {
spyOn(controller, method);
});
data.push(6, 7, 8);
data.push(9);
data.pop();
data.shift();
data.shift();
data.shift();
data.splice(1, 4, 10, 11);
data.unshift(12, 13, 14, 15);
data.unshift(16, 17);
[2, 1, 3, 1, 2].forEach(function(expected, index) {
expect(controller[methods[index]].calls.count()).toBe(expected);
});
});
it('should synchronize metadata when data are inserted or removed', function() {
var data = [0, 1, 2, 3, 4, 5];
var chart = acquireChart({
type: 'line',
data: {
datasets: [{
data: data
}]
}
});
var meta = chart.getDatasetMeta(0);
var first, second, last;
first = meta.data[0];
last = meta.data[5];
data.push(6, 7, 8);
data.push(9);
expect(meta.data.length).toBe(10);
expect(meta.data[0]).toBe(first);
expect(meta.data[5]).toBe(last);
last = meta.data[9];
data.pop();
expect(meta.data.length).toBe(9);
expect(meta.data[0]).toBe(first);
expect(meta.data.indexOf(last)).toBe(-1);
last = meta.data[8];
data.shift();
data.shift();
data.shift();
expect(meta.data.length).toBe(6);
expect(meta.data.indexOf(first)).toBe(-1);
expect(meta.data[5]).toBe(last);
first = meta.data[0];
second = meta.data[1];
last = meta.data[5];
data.splice(1, 4, 10, 11);
expect(meta.data.length).toBe(4);
expect(meta.data[0]).toBe(first);
expect(meta.data[3]).toBe(last);
expect(meta.data.indexOf(second)).toBe(-1);
data.unshift(12, 13, 14, 15);
data.unshift(16, 17);
expect(meta.data.length).toBe(10);
expect(meta.data[6]).toBe(first);
expect(meta.data[9]).toBe(last);
});
it('should re-synchronize metadata when the data object reference changes', function() {
var data0 = [0, 1, 2, 3, 4, 5];
var data1 = [6, 7, 8];
var chart = acquireChart({
type: 'line',
data: {
datasets: [{
data: data0
}]
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.data.length).toBe(6);
chart.data.datasets[0].data = data1;
chart.update();
expect(meta.data.length).toBe(3);
data1.push(9, 10, 11);
expect(meta.data.length).toBe(6);
});
it('should re-synchronize metadata when data are unusually altered', function() {
var data = [0, 1, 2, 3, 4, 5];
var chart = acquireChart({
type: 'line',
data: {
datasets: [{
data: data
}]
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.data.length).toBe(6);
data.length = 2;
chart.update();
expect(meta.data.length).toBe(2);
data.length = 42;
chart.update();
expect(meta.data.length).toBe(42);
});
it('should cleanup attached properties when the reference changes or when the chart is destroyed', function() {
var data0 = [0, 1, 2, 3, 4, 5];
var data1 = [6, 7, 8];
var chart = acquireChart({
type: 'line',
data: {
datasets: [{
data: data0
}]
}
});
var hooks = ['push', 'pop', 'shift', 'splice', 'unshift'];
expect(data0._chartjs).toBeDefined();
hooks.forEach(function(hook) {
expect(data0[hook]).not.toBe(Array.prototype[hook]);
});
expect(data1._chartjs).not.toBeDefined();
hooks.forEach(function(hook) {
expect(data1[hook]).toBe(Array.prototype[hook]);
});
chart.data.datasets[0].data = data1;
chart.update();
expect(data0._chartjs).not.toBeDefined();
hooks.forEach(function(hook) {
expect(data0[hook]).toBe(Array.prototype[hook]);
});
expect(data1._chartjs).toBeDefined();
hooks.forEach(function(hook) {
expect(data1[hook]).not.toBe(Array.prototype[hook]);
});
chart.destroy();
expect(data1._chartjs).not.toBeDefined();
hooks.forEach(function(hook) {
expect(data1[hook]).toBe(Array.prototype[hook]);
});
});
});

View File

@ -0,0 +1,44 @@
// Test the core element functionality
describe('Core element tests', function() {
it ('should transition model properties', function() {
var element = new Chart.Element({
_model: {
numberProp: 0,
numberProp2: 100,
_underscoreProp: 0,
stringProp: 'abc',
objectProp: {
myObject: true
},
colorProp: 'rgb(0, 0, 0)'
}
});
// First transition clones model into view
element.transition(0.25);
expect(element._view).toEqual(element._model);
expect(element._view.objectProp).toBe(element._model.objectProp); // not cloned
element._model.numberProp = 100;
element._model.numberProp2 = 250;
element._model._underscoreProp = 200;
element._model.stringProp = 'def';
element._model.newStringProp = 'newString';
element._model.colorProp = 'rgb(255, 255, 0)';
element.transition(0.25);
expect(element._view).toEqual({
numberProp: 25,
numberProp2: 137.5,
_underscoreProp: 0, // underscore props are not transition to a new value
stringProp: 'def',
newStringProp: 'newString',
objectProp: {
myObject: true
},
colorProp: 'rgb(64, 64, 0)',
});
});
});

View File

@ -0,0 +1,791 @@
describe('Core helper tests', function() {
var helpers;
beforeAll(function() {
helpers = window.Chart.helpers;
});
it('should merge a normal config without scales', function() {
var baseConfig = {
valueProp: 5,
arrayProp: [1, 2, 3, 4, 5, 6],
objectProp: {
prop1: 'abc',
prop2: 56
}
};
var toMerge = {
valueProp2: null,
arrayProp: ['a', 'c'],
objectProp: {
prop1: 'c',
prop3: 'prop3'
}
};
var merged = helpers.configMerge(baseConfig, toMerge);
expect(merged).toEqual({
valueProp: 5,
valueProp2: null,
arrayProp: ['a', 'c'],
objectProp: {
prop1: 'c',
prop2: 56,
prop3: 'prop3'
}
});
});
it('should merge scale configs', function() {
var baseConfig = {
scales: {
prop1: {
abc: 123,
def: '456'
},
prop2: 777,
yAxes: [{
type: 'linear',
}, {
type: 'log'
}]
}
};
var toMerge = {
scales: {
prop1: {
def: 'bbb',
ghi: 78
},
prop2: null,
yAxes: [{
type: 'linear',
axisProp: 456
}, {
// pulls in linear default config since axis type changes
type: 'linear',
position: 'right'
}, {
// Pulls in linear default config since axis not in base
type: 'linear'
}]
}
};
var merged = helpers.configMerge(baseConfig, toMerge);
expect(merged).toEqual({
scales: {
prop1: {
abc: 123,
def: 'bbb',
ghi: 78
},
prop2: null,
yAxes: [{
type: 'linear',
axisProp: 456
}, {
display: true,
gridLines: {
color: 'rgba(0, 0, 0, 0.1)',
drawBorder: true,
drawOnChartArea: true,
drawTicks: true, // draw ticks extending towards the label
tickMarkLength: 10,
lineWidth: 1,
offsetGridLines: false,
display: true,
zeroLineColor: 'rgba(0,0,0,0.25)',
zeroLineWidth: 1,
zeroLineBorderDash: [],
zeroLineBorderDashOffset: 0.0,
borderDash: [],
borderDashOffset: 0.0
},
position: 'right',
offset: false,
scaleLabel: Chart.defaults.scale.scaleLabel,
ticks: {
beginAtZero: false,
minRotation: 0,
maxRotation: 50,
mirror: false,
padding: 0,
reverse: false,
display: true,
callback: merged.scales.yAxes[1].ticks.callback, // make it nicer, then check explicitly below
autoSkip: true,
autoSkipPadding: 0,
labelOffset: 0,
minor: {},
major: {},
},
type: 'linear'
}, {
display: true,
gridLines: {
color: 'rgba(0, 0, 0, 0.1)',
drawBorder: true,
drawOnChartArea: true,
drawTicks: true, // draw ticks extending towards the label,
tickMarkLength: 10,
lineWidth: 1,
offsetGridLines: false,
display: true,
zeroLineColor: 'rgba(0,0,0,0.25)',
zeroLineWidth: 1,
zeroLineBorderDash: [],
zeroLineBorderDashOffset: 0.0,
borderDash: [],
borderDashOffset: 0.0
},
position: 'left',
offset: false,
scaleLabel: Chart.defaults.scale.scaleLabel,
ticks: {
beginAtZero: false,
minRotation: 0,
maxRotation: 50,
mirror: false,
padding: 0,
reverse: false,
display: true,
callback: merged.scales.yAxes[2].ticks.callback, // make it nicer, then check explicitly below
autoSkip: true,
autoSkipPadding: 0,
labelOffset: 0,
minor: {},
major: {},
},
type: 'linear'
}]
}
});
// Are these actually functions
expect(merged.scales.yAxes[1].ticks.callback).toEqual(jasmine.any(Function));
expect(merged.scales.yAxes[2].ticks.callback).toEqual(jasmine.any(Function));
});
it('should filter an array', function() {
var data = [-10, 0, 6, 0, 7];
var callback = function(item) {
return item > 2;
};
expect(helpers.where(data, callback)).toEqual([6, 7]);
expect(helpers.findNextWhere(data, callback)).toEqual(6);
expect(helpers.findNextWhere(data, callback, 2)).toBe(7);
expect(helpers.findNextWhere(data, callback, 4)).toBe(undefined);
expect(helpers.findPreviousWhere(data, callback)).toBe(7);
expect(helpers.findPreviousWhere(data, callback, 3)).toBe(6);
expect(helpers.findPreviousWhere(data, callback, 0)).toBe(undefined);
});
it('should get the correct sign', function() {
expect(helpers.sign(0)).toBe(0);
expect(helpers.sign(10)).toBe(1);
expect(helpers.sign(-5)).toBe(-1);
});
it('should do a log10 operation', function() {
expect(helpers.log10(0)).toBe(-Infinity);
// Check all allowed powers of 10, which should return integer values
var maxPowerOf10 = Math.floor(helpers.log10(Number.MAX_VALUE));
for (var i = 0; i < maxPowerOf10; i += 1) {
expect(helpers.log10(Math.pow(10, i))).toBe(i);
}
});
it('should correctly determine if two numbers are essentially equal', function() {
expect(helpers.almostEquals(0, Number.EPSILON, 2 * Number.EPSILON)).toBe(true);
expect(helpers.almostEquals(1, 1.1, 0.0001)).toBe(false);
expect(helpers.almostEquals(1e30, 1e30 + Number.EPSILON, 0)).toBe(false);
expect(helpers.almostEquals(1e30, 1e30 + Number.EPSILON, 2 * Number.EPSILON)).toBe(true);
});
it('should correctly determine if a numbers are essentially whole', function() {
expect(helpers.almostWhole(0.99999, 0.0001)).toBe(true);
expect(helpers.almostWhole(0.9, 0.0001)).toBe(false);
});
it('should generate integer ids', function() {
var uid = helpers.uid();
expect(uid).toEqual(jasmine.any(Number));
expect(helpers.uid()).toBe(uid + 1);
expect(helpers.uid()).toBe(uid + 2);
expect(helpers.uid()).toBe(uid + 3);
});
it('should detect a number', function() {
expect(helpers.isNumber(123)).toBe(true);
expect(helpers.isNumber('123')).toBe(true);
expect(helpers.isNumber(null)).toBe(false);
expect(helpers.isNumber(NaN)).toBe(false);
expect(helpers.isNumber(undefined)).toBe(false);
expect(helpers.isNumber('cbc')).toBe(false);
});
it('should convert between radians and degrees', function() {
expect(helpers.toRadians(180)).toBe(Math.PI);
expect(helpers.toRadians(90)).toBe(0.5 * Math.PI);
expect(helpers.toDegrees(Math.PI)).toBe(180);
expect(helpers.toDegrees(Math.PI * 3 / 2)).toBe(270);
});
it('should get an angle from a point', function() {
var center = {
x: 0,
y: 0
};
expect(helpers.getAngleFromPoint(center, {
x: 0,
y: 10
})).toEqual({
angle: Math.PI / 2,
distance: 10,
});
expect(helpers.getAngleFromPoint(center, {
x: Math.sqrt(2),
y: Math.sqrt(2)
})).toEqual({
angle: Math.PI / 4,
distance: 2
});
expect(helpers.getAngleFromPoint(center, {
x: -1.0 * Math.sqrt(2),
y: -1.0 * Math.sqrt(2)
})).toEqual({
angle: Math.PI * 1.25,
distance: 2
});
});
it('should spline curves', function() {
expect(helpers.splineCurve({
x: 0,
y: 0
}, {
x: 1,
y: 1
}, {
x: 2,
y: 0
}, 0)).toEqual({
previous: {
x: 1,
y: 1,
},
next: {
x: 1,
y: 1,
}
});
expect(helpers.splineCurve({
x: 0,
y: 0
}, {
x: 1,
y: 1
}, {
x: 2,
y: 0
}, 1)).toEqual({
previous: {
x: 0,
y: 1,
},
next: {
x: 2,
y: 1,
}
});
});
it('should spline curves with monotone cubic interpolation', function() {
var dataPoints = [
{_model: {x: 0, y: 0, skip: false}},
{_model: {x: 3, y: 6, skip: false}},
{_model: {x: 9, y: 6, skip: false}},
{_model: {x: 12, y: 60, skip: false}},
{_model: {x: 15, y: 60, skip: false}},
{_model: {x: 18, y: 120, skip: false}},
{_model: {x: null, y: null, skip: true}},
{_model: {x: 21, y: 180, skip: false}},
{_model: {x: 24, y: 120, skip: false}},
{_model: {x: 27, y: 125, skip: false}},
{_model: {x: 30, y: 105, skip: false}},
{_model: {x: 33, y: 110, skip: false}},
{_model: {x: 33, y: 110, skip: false}},
{_model: {x: 36, y: 170, skip: false}}
];
helpers.splineCurveMonotone(dataPoints);
expect(dataPoints).toEqual([{
_model: {
x: 0,
y: 0,
skip: false,
controlPointNextX: 1,
controlPointNextY: 2
}
},
{
_model: {
x: 3,
y: 6,
skip: false,
controlPointPreviousX: 2,
controlPointPreviousY: 6,
controlPointNextX: 5,
controlPointNextY: 6
}
},
{
_model: {
x: 9,
y: 6,
skip: false,
controlPointPreviousX: 7,
controlPointPreviousY: 6,
controlPointNextX: 10,
controlPointNextY: 6
}
},
{
_model: {
x: 12,
y: 60,
skip: false,
controlPointPreviousX: 11,
controlPointPreviousY: 60,
controlPointNextX: 13,
controlPointNextY: 60
}
},
{
_model: {
x: 15,
y: 60,
skip: false,
controlPointPreviousX: 14,
controlPointPreviousY: 60,
controlPointNextX: 16,
controlPointNextY: 60
}
},
{
_model: {
x: 18,
y: 120,
skip: false,
controlPointPreviousX: 17,
controlPointPreviousY: 100
}
},
{
_model: {
x: null,
y: null,
skip: true
}
},
{
_model: {
x: 21,
y: 180,
skip: false,
controlPointNextX: 22,
controlPointNextY: 160
}
},
{
_model: {
x: 24,
y: 120,
skip: false,
controlPointPreviousX: 23,
controlPointPreviousY: 120,
controlPointNextX: 25,
controlPointNextY: 120
}
},
{
_model: {
x: 27,
y: 125,
skip: false,
controlPointPreviousX: 26,
controlPointPreviousY: 125,
controlPointNextX: 28,
controlPointNextY: 125
}
},
{
_model: {
x: 30,
y: 105,
skip: false,
controlPointPreviousX: 29,
controlPointPreviousY: 105,
controlPointNextX: 31,
controlPointNextY: 105
}
},
{
_model: {
x: 33,
y: 110,
skip: false,
controlPointPreviousX: 32,
controlPointPreviousY: 110,
controlPointNextX: 33,
controlPointNextY: 110
}
},
{
_model: {
x: 33,
y: 110,
skip: false,
controlPointPreviousX: 33,
controlPointPreviousY: 110,
controlPointNextX: 34,
controlPointNextY: 110
}
},
{
_model: {
x: 36,
y: 170,
skip: false,
controlPointPreviousX: 35,
controlPointPreviousY: 150
}
}]);
});
it('should get the next or previous item in an array', function() {
var testData = [0, 1, 2];
expect(helpers.nextItem(testData, 0, false)).toEqual(1);
expect(helpers.nextItem(testData, 2, false)).toEqual(2);
expect(helpers.nextItem(testData, 2, true)).toEqual(0);
expect(helpers.nextItem(testData, 1, true)).toEqual(2);
expect(helpers.nextItem(testData, -1, false)).toEqual(0);
expect(helpers.previousItem(testData, 0, false)).toEqual(0);
expect(helpers.previousItem(testData, 0, true)).toEqual(2);
expect(helpers.previousItem(testData, 2, false)).toEqual(1);
expect(helpers.previousItem(testData, 1, true)).toEqual(0);
});
it('should return the width of the longest text in an Array and 2D Array', function() {
var context = window.createMockContext();
var font = "normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif";
var arrayOfThings1D = ['FooBar', 'Bar'];
var arrayOfThings2D = [['FooBar_1', 'Bar_2'], 'Foo_1'];
// Regardless 'FooBar' is the longest label it should return (characters * 10)
expect(helpers.longestText(context, font, arrayOfThings1D, {})).toEqual(60);
expect(helpers.longestText(context, font, arrayOfThings2D, {})).toEqual(80);
// We check to make sure we made the right calls to the canvas.
expect(context.getCalls()).toEqual([{
name: 'measureText',
args: ['FooBar']
}, {
name: 'measureText',
args: ['Bar']
}, {
name: 'measureText',
args: ['FooBar_1']
}, {
name: 'measureText',
args: ['Bar_2']
}, {
name: 'measureText',
args: ['Foo_1']
}]);
});
it('compare text with current longest and update', function() {
var context = window.createMockContext();
var data = {};
var gc = [];
var longest = 70;
expect(helpers.measureText(context, data, gc, longest, 'foobar')).toEqual(70);
expect(helpers.measureText(context, data, gc, longest, 'foobar_')).toEqual(70);
expect(helpers.measureText(context, data, gc, longest, 'foobar_1')).toEqual(80);
// We check to make sure we made the right calls to the canvas.
expect(context.getCalls()).toEqual([{
name: 'measureText',
args: ['foobar']
}, {
name: 'measureText',
args: ['foobar_']
}, {
name: 'measureText',
args: ['foobar_1']
}]);
});
it('count look at all the labels and return maximum number of lines', function() {
window.createMockContext();
var arrayOfThings1 = ['Foo', 'Bar'];
var arrayOfThings2 = [['Foo', 'Bar'], 'Foo'];
var arrayOfThings3 = [['Foo', 'Bar', 'Boo'], ['Foo', 'Bar'], 'Foo'];
expect(helpers.numberOfLabelLines(arrayOfThings1)).toEqual(1);
expect(helpers.numberOfLabelLines(arrayOfThings2)).toEqual(2);
expect(helpers.numberOfLabelLines(arrayOfThings3)).toEqual(3);
});
it ('should get the maximum width and height for a node', function() {
// Create div with fixed size as a test bed
var div = document.createElement('div');
div.style.width = '200px';
div.style.height = '300px';
document.body.appendChild(div);
// Create the div we want to get the max size for
var innerDiv = document.createElement('div');
div.appendChild(innerDiv);
expect(helpers.getMaximumWidth(innerDiv)).toBe(200);
expect(helpers.getMaximumHeight(innerDiv)).toBe(300);
document.body.removeChild(div);
});
it ('should get the maximum width of a node that has a max-width style', function() {
// Create div with fixed size as a test bed
var div = document.createElement('div');
div.style.width = '200px';
div.style.height = '300px';
document.body.appendChild(div);
// Create the div we want to get the max size for and set a max-width style
var innerDiv = document.createElement('div');
innerDiv.style.maxWidth = '150px';
div.appendChild(innerDiv);
expect(helpers.getMaximumWidth(innerDiv)).toBe(150);
document.body.removeChild(div);
});
it ('should get the maximum height of a node that has a max-height style', function() {
// Create div with fixed size as a test bed
var div = document.createElement('div');
div.style.width = '200px';
div.style.height = '300px';
document.body.appendChild(div);
// Create the div we want to get the max size for and set a max-height style
var innerDiv = document.createElement('div');
innerDiv.style.maxHeight = '150px';
div.appendChild(innerDiv);
expect(helpers.getMaximumHeight(innerDiv)).toBe(150);
document.body.removeChild(div);
});
it ('should get the maximum width of a node when the parent has a max-width style', function() {
// Create div with fixed size as a test bed
var div = document.createElement('div');
div.style.width = '200px';
div.style.height = '300px';
document.body.appendChild(div);
// Create an inner wrapper around our div we want to size and give that a max-width style
var parentDiv = document.createElement('div');
parentDiv.style.maxWidth = '150px';
div.appendChild(parentDiv);
// Create the div we want to get the max size for
var innerDiv = document.createElement('div');
parentDiv.appendChild(innerDiv);
expect(helpers.getMaximumWidth(innerDiv)).toBe(150);
document.body.removeChild(div);
});
it ('should get the maximum height of a node when the parent has a max-height style', function() {
// Create div with fixed size as a test bed
var div = document.createElement('div');
div.style.width = '200px';
div.style.height = '300px';
document.body.appendChild(div);
// Create an inner wrapper around our div we want to size and give that a max-height style
var parentDiv = document.createElement('div');
parentDiv.style.maxHeight = '150px';
div.appendChild(parentDiv);
// Create the div we want to get the max size for
var innerDiv = document.createElement('div');
innerDiv.style.height = '300px'; // make it large
parentDiv.appendChild(innerDiv);
expect(helpers.getMaximumHeight(innerDiv)).toBe(150);
document.body.removeChild(div);
});
it ('should get the maximum width of a node that has a percentage max-width style', function() {
// Create div with fixed size as a test bed
var div = document.createElement('div');
div.style.width = '200px';
div.style.height = '300px';
document.body.appendChild(div);
// Create the div we want to get the max size for and set a max-width style
var innerDiv = document.createElement('div');
innerDiv.style.maxWidth = '50%';
div.appendChild(innerDiv);
expect(helpers.getMaximumWidth(innerDiv)).toBe(100);
document.body.removeChild(div);
});
it ('should get the maximum height of a node that has a percentage max-height style', function() {
// Create div with fixed size as a test bed
var div = document.createElement('div');
div.style.width = '200px';
div.style.height = '300px';
document.body.appendChild(div);
// Create the div we want to get the max size for and set a max-height style
var innerDiv = document.createElement('div');
innerDiv.style.maxHeight = '50%';
div.appendChild(innerDiv);
expect(helpers.getMaximumHeight(innerDiv)).toBe(150);
document.body.removeChild(div);
});
it ('should get the maximum width of a node when the parent has a percentage max-width style', function() {
// Create div with fixed size as a test bed
var div = document.createElement('div');
div.style.width = '200px';
div.style.height = '300px';
document.body.appendChild(div);
// Create an inner wrapper around our div we want to size and give that a max-width style
var parentDiv = document.createElement('div');
parentDiv.style.maxWidth = '50%';
div.appendChild(parentDiv);
// Create the div we want to get the max size for
var innerDiv = document.createElement('div');
parentDiv.appendChild(innerDiv);
expect(helpers.getMaximumWidth(innerDiv)).toBe(100);
document.body.removeChild(div);
});
it ('should get the maximum height of a node when the parent has a percentage max-height style', function() {
// Create div with fixed size as a test bed
var div = document.createElement('div');
div.style.width = '200px';
div.style.height = '300px';
document.body.appendChild(div);
// Create an inner wrapper around our div we want to size and give that a max-height style
var parentDiv = document.createElement('div');
parentDiv.style.maxHeight = '50%';
div.appendChild(parentDiv);
var innerDiv = document.createElement('div');
innerDiv.style.height = '300px'; // make it large
parentDiv.appendChild(innerDiv);
expect(helpers.getMaximumHeight(innerDiv)).toBe(150);
document.body.removeChild(div);
});
it ('should leave styled height and width on canvas if explicitly set', function() {
var chart = window.acquireChart({}, {
canvas: {
height: 200,
width: 200,
style: 'height: 400px; width: 400px;'
}
});
helpers.retinaScale(chart, true);
var canvas = chart.canvas;
expect(canvas.style.height).toBe('400px');
expect(canvas.style.width).toBe('400px');
});
describe('Color helper', function() {
function isColorInstance(obj) {
return typeof obj === 'object' && obj.hasOwnProperty('values') && obj.values.hasOwnProperty('rgb');
}
it('should return a color when called with a color', function() {
expect(isColorInstance(helpers.color('rgb(1, 2, 3)'))).toBe(true);
});
it('should return a color when called with a CanvasGradient instance', function() {
var context = document.createElement('canvas').getContext('2d');
var gradient = context.createLinearGradient(0, 1, 2, 3);
expect(isColorInstance(helpers.color(gradient))).toBe(true);
});
});
describe('Background hover color helper', function() {
it('should return a CanvasPattern when called with a CanvasPattern', function(done) {
var dots = new Image();
dots.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAMAAAAolt3jAAAAD1BMVEUAAAD///////////////+PQt5oAAAABXRSTlMAHlFhZsfk/BEAAAAqSURBVHgBY2BgZGJmYmSAAUYWEIDzmcBcJhiXGcxlRpPFrhdmMiqgvX0AcGIBEUAo6UAAAAAASUVORK5CYII=';
dots.onload = function() {
var chartContext = document.createElement('canvas').getContext('2d');
var patternCanvas = document.createElement('canvas');
var patternContext = patternCanvas.getContext('2d');
var pattern = patternContext.createPattern(dots, 'repeat');
patternContext.fillStyle = pattern;
var backgroundColor = helpers.getHoverColor(chartContext.createPattern(patternCanvas, 'repeat'));
expect(backgroundColor instanceof CanvasPattern).toBe(true);
done();
};
});
it('should return a modified version of color when called with a color', function() {
var originalColorRGB = 'rgb(70, 191, 189)';
expect(helpers.getHoverColor('#46BFBD')).not.toEqual(originalColorRGB);
});
});
});

View File

@ -0,0 +1,739 @@
// Tests of the interaction handlers in Core.Interaction
// Test the rectangle element
describe('Core.Interaction', function() {
describe('point mode', function() {
beforeEach(function() {
this.chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
label: 'Dataset 1',
data: [10, 20, 30],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)'
}, {
label: 'Dataset 2',
data: [40, 20, 40],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
}
});
});
it ('should return all items under the point', function() {
var chart = this.chart;
var meta0 = chart.getDatasetMeta(0);
var meta1 = chart.getDatasetMeta(1);
var point = meta0.data[1];
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: point._model.x,
y: point._model.y,
};
var elements = Chart.Interaction.modes.point(chart, evt);
expect(elements).toEqual([point, meta1.data[1]]);
});
it ('should return an empty array when no items are found', function() {
var chart = this.chart;
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: 0,
y: 0
};
var elements = Chart.Interaction.modes.point(chart, evt);
expect(elements).toEqual([]);
});
});
describe('index mode', function() {
describe('intersect: true', function() {
beforeEach(function() {
this.chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
label: 'Dataset 1',
data: [10, 20, 30],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)'
}, {
label: 'Dataset 2',
data: [40, 40, 40],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
}
});
});
it ('gets correct items', function() {
var chart = this.chart;
var meta0 = chart.getDatasetMeta(0);
var meta1 = chart.getDatasetMeta(1);
var point = meta0.data[1];
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: point._model.x,
y: point._model.y,
};
var elements = Chart.Interaction.modes.index(chart, evt, {intersect: true});
expect(elements).toEqual([point, meta1.data[1]]);
});
it ('returns empty array when nothing found', function() {
var chart = this.chart;
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: 0,
y: 0,
};
var elements = Chart.Interaction.modes.index(chart, evt, {intersect: true});
expect(elements).toEqual([]);
});
});
describe ('intersect: false', function() {
var data = {
datasets: [{
label: 'Dataset 1',
data: [10, 20, 30],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)'
}, {
label: 'Dataset 2',
data: [40, 40, 40],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
};
beforeEach(function() {
this.chart = window.acquireChart({
type: 'line',
data: data
});
});
it ('axis: x gets correct items', function() {
var chart = this.chart;
var meta0 = chart.getDatasetMeta(0);
var meta1 = chart.getDatasetMeta(1);
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: 0,
y: 0
};
var elements = Chart.Interaction.modes.index(chart, evt, {intersect: false});
expect(elements).toEqual([meta0.data[0], meta1.data[0]]);
});
it ('axis: y gets correct items', function() {
var chart = window.acquireChart({
type: 'horizontalBar',
data: data
});
var meta0 = chart.getDatasetMeta(0);
var meta1 = chart.getDatasetMeta(1);
var center = meta0.data[0].getCenterPoint();
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: center.x,
y: center.y + 30,
};
var elements = Chart.Interaction.modes.index(chart, evt, {axis: 'y', intersect: false});
expect(elements).toEqual([meta0.data[0], meta1.data[0]]);
});
it ('axis: xy gets correct items', function() {
var chart = this.chart;
var meta0 = chart.getDatasetMeta(0);
var meta1 = chart.getDatasetMeta(1);
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: 0,
y: 0
};
var elements = Chart.Interaction.modes.index(chart, evt, {axis: 'xy', intersect: false});
expect(elements).toEqual([meta0.data[0], meta1.data[0]]);
});
});
});
describe('dataset mode', function() {
describe('intersect: true', function() {
beforeEach(function() {
this.chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
label: 'Dataset 1',
data: [10, 20, 30],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)'
}, {
label: 'Dataset 2',
data: [40, 40, 40],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
}
});
});
it ('should return all items in the dataset of the first item found', function() {
var chart = this.chart;
var meta = chart.getDatasetMeta(0);
var point = meta.data[1];
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: point._model.x,
y: point._model.y
};
var elements = Chart.Interaction.modes.dataset(chart, evt, {intersect: true});
expect(elements).toEqual(meta.data);
});
it ('should return an empty array if nothing found', function() {
var chart = this.chart;
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: 0,
y: 0
};
var elements = Chart.Interaction.modes.dataset(chart, evt, {intersect: true});
expect(elements).toEqual([]);
});
});
describe('intersect: false', function() {
var data = {
datasets: [{
label: 'Dataset 1',
data: [10, 20, 30],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)'
}, {
label: 'Dataset 2',
data: [40, 40, 40],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
};
beforeEach(function() {
this.chart = window.acquireChart({
type: 'line',
data: data
});
});
it ('axis: x gets correct items', function() {
var chart = window.acquireChart({
type: 'horizontalBar',
data: data
});
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: 0,
y: 0
};
var elements = Chart.Interaction.modes.dataset(chart, evt, {axis: 'x', intersect: false});
var meta = chart.getDatasetMeta(0);
expect(elements).toEqual(meta.data);
});
it ('axis: y gets correct items', function() {
var chart = this.chart;
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: 0,
y: 0
};
var elements = Chart.Interaction.modes.dataset(chart, evt, {axis: 'y', intersect: false});
var meta = chart.getDatasetMeta(1);
expect(elements).toEqual(meta.data);
});
it ('axis: xy gets correct items', function() {
var chart = this.chart;
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: 0,
y: 0
};
var elements = Chart.Interaction.modes.dataset(chart, evt, {intersect: false});
var meta = chart.getDatasetMeta(1);
expect(elements).toEqual(meta.data);
});
});
});
describe('nearest mode', function() {
describe('intersect: false', function() {
beforeEach(function() {
this.chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
label: 'Dataset 1',
data: [10, 40, 30],
pointRadius: [5, 5, 5],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)'
}, {
label: 'Dataset 2',
data: [40, 40, 40],
pointRadius: [10, 10, 10],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
}
});
});
it ('axis: xy should return the nearest item', function() {
var chart = this.chart;
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: 0,
y: 0
};
// Nearest to 0,0 (top left) will be first point of dataset 2
var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: false});
var meta = chart.getDatasetMeta(1);
expect(elements).toEqual([meta.data[0]]);
});
it ('should return the smallest item if more than 1 are at the same distance', function() {
var chart = this.chart;
var meta0 = chart.getDatasetMeta(0);
var meta1 = chart.getDatasetMeta(1);
// Halfway between 2 mid points
var pt = {
x: meta0.data[1]._view.x,
y: (meta0.data[1]._view.y + meta1.data[1]._view.y) / 2
};
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: pt.x,
y: pt.y
};
// Nearest to 0,0 (top left) will be first point of dataset 2
var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: false});
expect(elements).toEqual([meta0.data[1]]);
});
it ('should return the lowest dataset index if size and area are the same', function() {
var chart = this.chart;
// Make equal sized points at index: 1
chart.data.datasets[0].pointRadius[1] = 10;
chart.update();
// Trigger an event over top of the
var meta0 = chart.getDatasetMeta(0);
var meta1 = chart.getDatasetMeta(1);
// Halfway between 2 mid points
var pt = {
x: meta0.data[1]._view.x,
y: (meta0.data[1]._view.y + meta1.data[1]._view.y) / 2
};
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: pt.x,
y: pt.y
};
// Nearest to 0,0 (top left) will be first point of dataset 2
var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: false});
expect(elements).toEqual([meta0.data[1]]);
});
});
describe('intersect: true', function() {
beforeEach(function() {
this.chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
label: 'Dataset 1',
data: [10, 20, 30],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)'
}, {
label: 'Dataset 2',
data: [40, 40, 40],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
}
});
});
it ('should return the nearest item', function() {
var chart = this.chart;
var meta = chart.getDatasetMeta(1);
var point = meta.data[1];
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: point._view.x + 15,
y: point._view.y
};
// Nothing intersects so find nothing
var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true});
expect(elements).toEqual([]);
evt = {
type: 'click',
chart: chart,
native: true,
x: point._view.x,
y: point._view.y
};
elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true});
expect(elements).toEqual([point]);
});
it ('should return the nearest item even if 2 intersect', function() {
var chart = this.chart;
chart.data.datasets[0].pointRadius = [5, 30, 5];
chart.data.datasets[0].data[1] = 39;
chart.data.datasets[1].pointRadius = [10, 10, 10];
// Trigger an event over top of the
var meta0 = chart.getDatasetMeta(0);
// Halfway between 2 mid points
var pt = {
x: meta0.data[1]._view.x,
y: meta0.data[1]._view.y
};
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: pt.x,
y: pt.y
};
var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true});
expect(elements).toEqual([meta0.data[1]]);
});
it ('should return the smallest item if more than 1 are at the same distance', function() {
var chart = this.chart;
chart.data.datasets[0].pointRadius = [5, 5, 5];
chart.data.datasets[0].data[1] = 40;
chart.data.datasets[1].pointRadius = [10, 10, 10];
// Trigger an event over top of the
var meta0 = chart.getDatasetMeta(0);
// Halfway between 2 mid points
var pt = {
x: meta0.data[1]._view.x,
y: meta0.data[1]._view.y
};
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: pt.x,
y: pt.y
};
var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true});
expect(elements).toEqual([meta0.data[1]]);
});
it ('should return the item at the lowest dataset index if distance and area are the same', function() {
var chart = this.chart;
chart.data.datasets[0].pointRadius = [5, 10, 5];
chart.data.datasets[0].data[1] = 40;
chart.data.datasets[1].pointRadius = [10, 10, 10];
// Trigger an event over top of the
var meta0 = chart.getDatasetMeta(0);
// Halfway between 2 mid points
var pt = {
x: meta0.data[1]._view.x,
y: meta0.data[1]._view.y
};
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: pt.x,
y: pt.y
};
// Nearest to 0,0 (top left) will be first point of dataset 2
var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true});
expect(elements).toEqual([meta0.data[1]]);
});
});
});
describe('x mode', function() {
beforeEach(function() {
this.chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
label: 'Dataset 1',
data: [10, 40, 30],
pointRadius: [5, 10, 5],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)'
}, {
label: 'Dataset 2',
data: [40, 40, 40],
pointRadius: [10, 10, 10],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
}
});
});
it('should return items at the same x value when intersect is false', function() {
var chart = this.chart;
var meta0 = chart.getDatasetMeta(0);
var meta1 = chart.getDatasetMeta(1);
// Halfway between 2 mid points
var pt = {
x: meta0.data[1]._view.x,
y: meta0.data[1]._view.y
};
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: pt.x,
y: 0
};
var elements = Chart.Interaction.modes.x(chart, evt, {intersect: false});
expect(elements).toEqual([meta0.data[1], meta1.data[1]]);
evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: pt.x + 20,
y: 0
};
elements = Chart.Interaction.modes.x(chart, evt, {intersect: false});
expect(elements).toEqual([]);
});
it('should return items at the same x value when intersect is true', function() {
var chart = this.chart;
var meta0 = chart.getDatasetMeta(0);
var meta1 = chart.getDatasetMeta(1);
// Halfway between 2 mid points
var pt = {
x: meta0.data[1]._view.x,
y: meta0.data[1]._view.y
};
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: pt.x,
y: 0
};
var elements = Chart.Interaction.modes.x(chart, evt, {intersect: true});
expect(elements).toEqual([]); // we don't intersect anything
evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: pt.x,
y: pt.y
};
elements = Chart.Interaction.modes.x(chart, evt, {intersect: true});
expect(elements).toEqual([meta0.data[1], meta1.data[1]]);
});
});
describe('y mode', function() {
beforeEach(function() {
this.chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
label: 'Dataset 1',
data: [10, 40, 30],
pointRadius: [5, 10, 5],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)'
}, {
label: 'Dataset 2',
data: [40, 40, 40],
pointRadius: [10, 10, 10],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
}
});
});
it('should return items at the same y value when intersect is false', function() {
var chart = this.chart;
var meta0 = chart.getDatasetMeta(0);
var meta1 = chart.getDatasetMeta(1);
// Halfway between 2 mid points
var pt = {
x: meta0.data[1]._view.x,
y: meta0.data[1]._view.y
};
var evt = {
type: 'click',
chart: chart,
native: true,
x: 0,
y: pt.y,
};
var elements = Chart.Interaction.modes.y(chart, evt, {intersect: false});
expect(elements).toEqual([meta0.data[1], meta1.data[0], meta1.data[1], meta1.data[2]]);
evt = {
type: 'click',
chart: chart,
native: true,
x: pt.x,
y: pt.y + 20, // out of range
};
elements = Chart.Interaction.modes.y(chart, evt, {intersect: false});
expect(elements).toEqual([]);
});
it('should return items at the same y value when intersect is true', function() {
var chart = this.chart;
var meta0 = chart.getDatasetMeta(0);
var meta1 = chart.getDatasetMeta(1);
// Halfway between 2 mid points
var pt = {
x: meta0.data[1]._view.x,
y: meta0.data[1]._view.y
};
var evt = {
type: 'click',
chart: chart,
native: true,
x: 0,
y: pt.y
};
var elements = Chart.Interaction.modes.y(chart, evt, {intersect: true});
expect(elements).toEqual([]); // we don't intersect anything
evt = {
type: 'click',
chart: chart,
native: true,
x: pt.x,
y: pt.y,
};
elements = Chart.Interaction.modes.y(chart, evt, {intersect: true});
expect(elements).toEqual([meta0.data[1], meta1.data[0], meta1.data[1], meta1.data[2]]);
});
});
});

View File

@ -0,0 +1,592 @@
describe('Chart.layouts', function() {
it('should be exposed through Chart.layouts', function() {
expect(Chart.layouts).toBeDefined();
expect(typeof Chart.layouts).toBe('object');
expect(Chart.layouts.defaults).toBeDefined();
expect(Chart.layouts.addBox).toBeDefined();
expect(Chart.layouts.removeBox).toBeDefined();
expect(Chart.layouts.configure).toBeDefined();
expect(Chart.layouts.update).toBeDefined();
});
// Disable tests which need to be rewritten based on changes introduced by
// the following changes: https://github.com/chartjs/Chart.js/pull/2346
// using xit marks the test as pending: http://jasmine.github.io/2.0/introduction.html#section-Pending_Specs
xit('should fit a simple chart with 2 scales', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [
{data: [10, 5, 0, 25, 78, -10]}
],
labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']
},
options: {
scales: {
xAxes: [{
id: 'xScale',
type: 'category'
}],
yAxes: [{
id: 'yScale',
type: 'linear'
}]
}
}
}, {
canvas: {
height: 150,
width: 250
}
});
expect(chart.chartArea.bottom).toBeCloseToPixel(112);
expect(chart.chartArea.left).toBeCloseToPixel(41);
expect(chart.chartArea.right).toBeCloseToPixel(250);
expect(chart.chartArea.top).toBeCloseToPixel(32);
// Is xScale at the right spot
expect(chart.scales.xScale.bottom).toBeCloseToPixel(150);
expect(chart.scales.xScale.left).toBeCloseToPixel(41);
expect(chart.scales.xScale.right).toBeCloseToPixel(250);
expect(chart.scales.xScale.top).toBeCloseToPixel(112);
expect(chart.scales.xScale.labelRotation).toBeCloseTo(25);
// Is yScale at the right spot
expect(chart.scales.yScale.bottom).toBeCloseToPixel(112);
expect(chart.scales.yScale.left).toBeCloseToPixel(0);
expect(chart.scales.yScale.right).toBeCloseToPixel(41);
expect(chart.scales.yScale.top).toBeCloseToPixel(32);
expect(chart.scales.yScale.labelRotation).toBeCloseTo(0);
});
xit('should fit scales that are in the top and right positions', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [
{data: [10, 5, 0, 25, 78, -10]}
],
labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']
},
options: {
scales: {
xAxes: [{
id: 'xScale',
type: 'category',
position: 'top'
}],
yAxes: [{
id: 'yScale',
type: 'linear',
position: 'right'
}]
}
}
}, {
canvas: {
height: 150,
width: 250
}
});
expect(chart.chartArea.bottom).toBeCloseToPixel(150);
expect(chart.chartArea.left).toBeCloseToPixel(0);
expect(chart.chartArea.right).toBeCloseToPixel(209);
expect(chart.chartArea.top).toBeCloseToPixel(71);
// Is xScale at the right spot
expect(chart.scales.xScale.bottom).toBeCloseToPixel(71);
expect(chart.scales.xScale.left).toBeCloseToPixel(0);
expect(chart.scales.xScale.right).toBeCloseToPixel(209);
expect(chart.scales.xScale.top).toBeCloseToPixel(32);
expect(chart.scales.xScale.labelRotation).toBeCloseTo(25);
// Is yScale at the right spot
expect(chart.scales.yScale.bottom).toBeCloseToPixel(150);
expect(chart.scales.yScale.left).toBeCloseToPixel(209);
expect(chart.scales.yScale.right).toBeCloseToPixel(250);
expect(chart.scales.yScale.top).toBeCloseToPixel(71);
expect(chart.scales.yScale.labelRotation).toBeCloseTo(0);
});
it('should fit scales that overlap the chart area', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [10, 5, 0, 25, 78, -10]
}, {
data: [-19, -20, 0, -99, -50, 0]
}],
labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']
}
});
expect(chart.chartArea.bottom).toBeCloseToPixel(512);
expect(chart.chartArea.left).toBeCloseToPixel(0);
expect(chart.chartArea.right).toBeCloseToPixel(512);
expect(chart.chartArea.top).toBeCloseToPixel(32);
expect(chart.scale.bottom).toBeCloseToPixel(512);
expect(chart.scale.left).toBeCloseToPixel(0);
expect(chart.scale.right).toBeCloseToPixel(512);
expect(chart.scale.top).toBeCloseToPixel(32);
expect(chart.scale.width).toBeCloseToPixel(512);
expect(chart.scale.height).toBeCloseToPixel(480);
});
xit('should fit multiple axes in the same position', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
yAxisID: 'yScale1',
data: [10, 5, 0, 25, 78, -10]
}, {
yAxisID: 'yScale2',
data: [-19, -20, 0, -99, -50, 0]
}],
labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']
},
options: {
scales: {
xAxes: [{
id: 'xScale',
type: 'category'
}],
yAxes: [{
id: 'yScale1',
type: 'linear'
}, {
id: 'yScale2',
type: 'linear'
}]
}
}
}, {
canvas: {
height: 150,
width: 250
}
});
expect(chart.chartArea.bottom).toBeCloseToPixel(102);
expect(chart.chartArea.left).toBeCloseToPixel(86);
expect(chart.chartArea.right).toBeCloseToPixel(250);
expect(chart.chartArea.top).toBeCloseToPixel(32);
// Is xScale at the right spot
expect(chart.scales.xScale.bottom).toBeCloseToPixel(150);
expect(chart.scales.xScale.left).toBeCloseToPixel(86);
expect(chart.scales.xScale.right).toBeCloseToPixel(250);
expect(chart.scales.xScale.top).toBeCloseToPixel(103);
expect(chart.scales.xScale.labelRotation).toBeCloseTo(50);
// Are yScales at the right spot
expect(chart.scales.yScale1.bottom).toBeCloseToPixel(102);
expect(chart.scales.yScale1.left).toBeCloseToPixel(0);
expect(chart.scales.yScale1.right).toBeCloseToPixel(41);
expect(chart.scales.yScale1.top).toBeCloseToPixel(32);
expect(chart.scales.yScale1.labelRotation).toBeCloseTo(0);
expect(chart.scales.yScale2.bottom).toBeCloseToPixel(102);
expect(chart.scales.yScale2.left).toBeCloseToPixel(41);
expect(chart.scales.yScale2.right).toBeCloseToPixel(86);
expect(chart.scales.yScale2.top).toBeCloseToPixel(32);
expect(chart.scales.yScale2.labelRotation).toBeCloseTo(0);
});
xit ('should fix a full width box correctly', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
xAxisID: 'xScale1',
data: [10, 5, 0, 25, 78, -10]
}, {
xAxisID: 'xScale2',
data: [-19, -20, 0, -99, -50, 0]
}],
labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']
},
options: {
scales: {
xAxes: [{
id: 'xScale1',
type: 'category'
}, {
id: 'xScale2',
type: 'category',
position: 'top',
fullWidth: true
}],
yAxes: [{
id: 'yScale',
type: 'linear'
}]
}
}
});
expect(chart.chartArea.bottom).toBeCloseToPixel(484);
expect(chart.chartArea.left).toBeCloseToPixel(45);
expect(chart.chartArea.right).toBeCloseToPixel(512);
expect(chart.chartArea.top).toBeCloseToPixel(60);
// Are xScales at the right spot
expect(chart.scales.xScale1.bottom).toBeCloseToPixel(512);
expect(chart.scales.xScale1.left).toBeCloseToPixel(45);
expect(chart.scales.xScale1.right).toBeCloseToPixel(512);
expect(chart.scales.xScale1.top).toBeCloseToPixel(484);
expect(chart.scales.xScale2.bottom).toBeCloseToPixel(60);
expect(chart.scales.xScale2.left).toBeCloseToPixel(0);
expect(chart.scales.xScale2.right).toBeCloseToPixel(512);
expect(chart.scales.xScale2.top).toBeCloseToPixel(32);
// Is yScale at the right spot
expect(chart.scales.yScale.bottom).toBeCloseToPixel(484);
expect(chart.scales.yScale.left).toBeCloseToPixel(0);
expect(chart.scales.yScale.right).toBeCloseToPixel(45);
expect(chart.scales.yScale.top).toBeCloseToPixel(60);
});
describe('padding settings', function() {
it('should apply a single padding to all dimensions', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [
{
data: [10, 5, 0, 25, 78, -10]
}
],
labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']
},
options: {
scales: {
xAxes: [{
id: 'xScale',
type: 'category',
display: false
}],
yAxes: [{
id: 'yScale',
type: 'linear',
display: false
}]
},
legend: {
display: false
},
title: {
display: false
},
layout: {
padding: 10
}
}
}, {
canvas: {
height: 150,
width: 250
}
});
expect(chart.chartArea.bottom).toBeCloseToPixel(140);
expect(chart.chartArea.left).toBeCloseToPixel(10);
expect(chart.chartArea.right).toBeCloseToPixel(240);
expect(chart.chartArea.top).toBeCloseToPixel(10);
});
it('should apply padding in all positions', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [
{
data: [10, 5, 0, 25, 78, -10]
}
],
labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']
},
options: {
scales: {
xAxes: [{
id: 'xScale',
type: 'category',
display: false
}],
yAxes: [{
id: 'yScale',
type: 'linear',
display: false
}]
},
legend: {
display: false
},
title: {
display: false
},
layout: {
padding: {
left: 5,
right: 15,
top: 8,
bottom: 12
}
}
}
}, {
canvas: {
height: 150,
width: 250
}
});
expect(chart.chartArea.bottom).toBeCloseToPixel(138);
expect(chart.chartArea.left).toBeCloseToPixel(5);
expect(chart.chartArea.right).toBeCloseToPixel(235);
expect(chart.chartArea.top).toBeCloseToPixel(8);
});
it('should default to 0 padding if no dimensions specified', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [
{
data: [10, 5, 0, 25, 78, -10]
}
],
labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']
},
options: {
scales: {
xAxes: [{
id: 'xScale',
type: 'category',
display: false
}],
yAxes: [{
id: 'yScale',
type: 'linear',
display: false
}]
},
legend: {
display: false
},
title: {
display: false
},
layout: {
padding: {}
}
}
}, {
canvas: {
height: 150,
width: 250
}
});
expect(chart.chartArea.bottom).toBeCloseToPixel(150);
expect(chart.chartArea.left).toBeCloseToPixel(0);
expect(chart.chartArea.right).toBeCloseToPixel(250);
expect(chart.chartArea.top).toBeCloseToPixel(0);
});
});
describe('ordering by weight', function() {
it('should keep higher weights outside', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [
{
data: [10, 5, 0, 25, 78, -10]
}
],
labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']
},
options: {
legend: {
display: true,
position: 'left',
},
title: {
display: true,
position: 'bottom',
},
},
}, {
canvas: {
height: 150,
width: 250
}
});
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
var legend = chart.legend;
var title = chart.titleBlock;
expect(yAxis.left).toBe(legend.right);
expect(xAxis.bottom).toBe(title.top);
});
it('should correctly set weights of scales and order them', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [
{
data: [10, 5, 0, 25, 78, -10]
}
],
labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']
},
options: {
scales: {
xAxes: [{
id: 'xScale0',
type: 'category',
display: true,
weight: 1
}, {
id: 'xScale1',
type: 'category',
display: true,
weight: 2
}, {
id: 'xScale2',
type: 'category',
display: true
}, {
id: 'xScale3',
type: 'category',
display: true,
position: 'top',
weight: 1
}, {
id: 'xScale4',
type: 'category',
display: true,
position: 'top',
weight: 2
}],
yAxes: [{
id: 'yScale0',
type: 'linear',
display: true,
weight: 1
}, {
id: 'yScale1',
type: 'linear',
display: true,
weight: 2
}, {
id: 'yScale2',
type: 'linear',
display: true
}, {
id: 'yScale3',
type: 'linear',
display: true,
position: 'right',
weight: 1
}, {
id: 'yScale4',
type: 'linear',
display: true,
position: 'right',
weight: 2
}]
}
}
}, {
canvas: {
height: 150,
width: 250
}
});
var xScale0 = chart.scales.xScale0;
var xScale1 = chart.scales.xScale1;
var xScale2 = chart.scales.xScale2;
var xScale3 = chart.scales.xScale3;
var xScale4 = chart.scales.xScale4;
var yScale0 = chart.scales.yScale0;
var yScale1 = chart.scales.yScale1;
var yScale2 = chart.scales.yScale2;
var yScale3 = chart.scales.yScale3;
var yScale4 = chart.scales.yScale4;
expect(xScale0.weight).toBe(1);
expect(xScale1.weight).toBe(2);
expect(xScale2.weight).toBe(0);
expect(xScale3.weight).toBe(1);
expect(xScale4.weight).toBe(2);
expect(yScale0.weight).toBe(1);
expect(yScale1.weight).toBe(2);
expect(yScale2.weight).toBe(0);
expect(yScale3.weight).toBe(1);
expect(yScale4.weight).toBe(2);
var isOrderCorrect = false;
// bottom axes
isOrderCorrect = xScale2.top < xScale0.top && xScale0.top < xScale1.top;
expect(isOrderCorrect).toBe(true);
// top axes
isOrderCorrect = xScale4.top < xScale3.top;
expect(isOrderCorrect).toBe(true);
// left axes
isOrderCorrect = yScale1.left < yScale0.left && yScale0.left < yScale2.left;
expect(isOrderCorrect).toBe(true);
// right axes
isOrderCorrect = yScale3.left < yScale4.left;
expect(isOrderCorrect).toBe(true);
});
});
describe('box sizing', function() {
it('should correctly compute y-axis width to fit labels', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
labels: ['tick 1', 'tick 2', 'tick 3', 'tick 4', 'tick 5'],
datasets: [{
data: [0, 2.25, 1.5, 1.25, 2.5]
}],
},
options: {
legend: {
display: false,
},
},
}, {
canvas: {
height: 256,
width: 256
}
});
var yAxis = chart.scales['y-axis-0'];
// issue #4441: y-axis labels partially hidden.
// minimum horizontal space required to fit labels
expect(yAxis.width).toBeCloseToPixel(33);
expect(yAxis.ticks).toEqual(['2.5', '2.0', '1.5', '1.0', '0.5', '0']);
});
});
});

View File

@ -0,0 +1,377 @@
describe('Chart.plugins', function() {
beforeEach(function() {
this._plugins = Chart.plugins.getAll();
Chart.plugins.clear();
});
afterEach(function() {
Chart.plugins.clear();
Chart.plugins.register(this._plugins);
delete this._plugins;
});
describe('Chart.plugins.register', function() {
it('should register a plugin', function() {
Chart.plugins.register({});
expect(Chart.plugins.count()).toBe(1);
Chart.plugins.register({});
expect(Chart.plugins.count()).toBe(2);
});
it('should register an array of plugins', function() {
Chart.plugins.register([{}, {}, {}]);
expect(Chart.plugins.count()).toBe(3);
});
it('should succeed to register an already registered plugin', function() {
var plugin = {};
Chart.plugins.register(plugin);
expect(Chart.plugins.count()).toBe(1);
Chart.plugins.register(plugin);
expect(Chart.plugins.count()).toBe(1);
Chart.plugins.register([{}, plugin, plugin]);
expect(Chart.plugins.count()).toBe(2);
});
});
describe('Chart.plugins.unregister', function() {
it('should unregister a plugin', function() {
var plugin = {};
Chart.plugins.register(plugin);
expect(Chart.plugins.count()).toBe(1);
Chart.plugins.unregister(plugin);
expect(Chart.plugins.count()).toBe(0);
});
it('should unregister an array of plugins', function() {
var plugins = [{}, {}, {}];
Chart.plugins.register(plugins);
expect(Chart.plugins.count()).toBe(3);
Chart.plugins.unregister(plugins.slice(0, 2));
expect(Chart.plugins.count()).toBe(1);
});
it('should succeed to unregister a plugin not registered', function() {
var plugin = {};
Chart.plugins.register(plugin);
expect(Chart.plugins.count()).toBe(1);
Chart.plugins.unregister({});
expect(Chart.plugins.count()).toBe(1);
Chart.plugins.unregister([{}, plugin]);
expect(Chart.plugins.count()).toBe(0);
});
});
describe('Chart.plugins.notify', function() {
it('should call inline plugins with arguments', function() {
var plugin = {hook: function() {}};
var chart = window.acquireChart({
plugins: [plugin]
});
spyOn(plugin, 'hook');
Chart.plugins.notify(chart, 'hook', 42);
expect(plugin.hook.calls.count()).toBe(1);
expect(plugin.hook.calls.first().args[0]).toBe(chart);
expect(plugin.hook.calls.first().args[1]).toBe(42);
expect(plugin.hook.calls.first().args[2]).toEqual({});
});
it('should call global plugins with arguments', function() {
var plugin = {hook: function() {}};
var chart = window.acquireChart({});
spyOn(plugin, 'hook');
Chart.plugins.register(plugin);
Chart.plugins.notify(chart, 'hook', 42);
expect(plugin.hook.calls.count()).toBe(1);
expect(plugin.hook.calls.first().args[0]).toBe(chart);
expect(plugin.hook.calls.first().args[1]).toBe(42);
expect(plugin.hook.calls.first().args[2]).toEqual({});
});
it('should call plugin only once even if registered multiple times', function() {
var plugin = {hook: function() {}};
var chart = window.acquireChart({
plugins: [plugin, plugin]
});
spyOn(plugin, 'hook');
Chart.plugins.register([plugin, plugin]);
Chart.plugins.notify(chart, 'hook');
expect(plugin.hook.calls.count()).toBe(1);
});
it('should call plugins in the correct order (global first)', function() {
var results = [];
var chart = window.acquireChart({
plugins: [{
hook: function() {
results.push(1);
}
}, {
hook: function() {
results.push(2);
}
}, {
hook: function() {
results.push(3);
}
}]
});
Chart.plugins.register([{
hook: function() {
results.push(4);
}
}, {
hook: function() {
results.push(5);
}
}, {
hook: function() {
results.push(6);
}
}]);
var ret = Chart.plugins.notify(chart, 'hook');
expect(ret).toBeTruthy();
expect(results).toEqual([4, 5, 6, 1, 2, 3]);
});
it('should return TRUE if no plugin explicitly returns FALSE', function() {
var chart = window.acquireChart({
plugins: [{
hook: function() {}
}, {
hook: function() {
return null;
}
}, {
hook: function() {
return 0;
}
}, {
hook: function() {
return true;
}
}, {
hook: function() {
return 1;
}
}]
});
var plugins = chart.config.plugins;
plugins.forEach(function(plugin) {
spyOn(plugin, 'hook').and.callThrough();
});
var ret = Chart.plugins.notify(chart, 'hook');
expect(ret).toBeTruthy();
plugins.forEach(function(plugin) {
expect(plugin.hook).toHaveBeenCalled();
});
});
it('should return FALSE if any plugin explicitly returns FALSE', function() {
var chart = window.acquireChart({
plugins: [{
hook: function() {}
}, {
hook: function() {
return null;
}
}, {
hook: function() {
return false;
}
}, {
hook: function() {
return 42;
}
}, {
hook: function() {
return 'bar';
}
}]
});
var plugins = chart.config.plugins;
plugins.forEach(function(plugin) {
spyOn(plugin, 'hook').and.callThrough();
});
var ret = Chart.plugins.notify(chart, 'hook');
expect(ret).toBeFalsy();
expect(plugins[0].hook).toHaveBeenCalled();
expect(plugins[1].hook).toHaveBeenCalled();
expect(plugins[2].hook).toHaveBeenCalled();
expect(plugins[3].hook).not.toHaveBeenCalled();
expect(plugins[4].hook).not.toHaveBeenCalled();
});
});
describe('config.options.plugins', function() {
it('should call plugins with options at last argument', function() {
var plugin = {id: 'foo', hook: function() {}};
var chart = window.acquireChart({
options: {
plugins: {
foo: {a: '123'},
}
}
});
spyOn(plugin, 'hook');
Chart.plugins.register(plugin);
Chart.plugins.notify(chart, 'hook');
Chart.plugins.notify(chart, 'hook', ['bla']);
Chart.plugins.notify(chart, 'hook', ['bla', 42]);
expect(plugin.hook.calls.count()).toBe(3);
expect(plugin.hook.calls.argsFor(0)[1]).toEqual({a: '123'});
expect(plugin.hook.calls.argsFor(1)[2]).toEqual({a: '123'});
expect(plugin.hook.calls.argsFor(2)[3]).toEqual({a: '123'});
});
it('should call plugins with options associated to their identifier', function() {
var plugins = {
a: {id: 'a', hook: function() {}},
b: {id: 'b', hook: function() {}},
c: {id: 'c', hook: function() {}}
};
Chart.plugins.register(plugins.a);
var chart = window.acquireChart({
plugins: [plugins.b, plugins.c],
options: {
plugins: {
a: {a: '123'},
b: {b: '456'},
c: {c: '789'}
}
}
});
spyOn(plugins.a, 'hook');
spyOn(plugins.b, 'hook');
spyOn(plugins.c, 'hook');
Chart.plugins.notify(chart, 'hook');
expect(plugins.a.hook).toHaveBeenCalled();
expect(plugins.b.hook).toHaveBeenCalled();
expect(plugins.c.hook).toHaveBeenCalled();
expect(plugins.a.hook.calls.first().args[1]).toEqual({a: '123'});
expect(plugins.b.hook.calls.first().args[1]).toEqual({b: '456'});
expect(plugins.c.hook.calls.first().args[1]).toEqual({c: '789'});
});
it('should not called plugins when config.options.plugins.{id} is FALSE', function() {
var plugins = {
a: {id: 'a', hook: function() {}},
b: {id: 'b', hook: function() {}},
c: {id: 'c', hook: function() {}}
};
Chart.plugins.register(plugins.a);
var chart = window.acquireChart({
plugins: [plugins.b, plugins.c],
options: {
plugins: {
a: false,
b: false
}
}
});
spyOn(plugins.a, 'hook');
spyOn(plugins.b, 'hook');
spyOn(plugins.c, 'hook');
Chart.plugins.notify(chart, 'hook');
expect(plugins.a.hook).not.toHaveBeenCalled();
expect(plugins.b.hook).not.toHaveBeenCalled();
expect(plugins.c.hook).toHaveBeenCalled();
});
it('should call plugins with default options when plugin options is TRUE', function() {
var plugin = {id: 'a', hook: function() {}};
Chart.defaults.global.plugins.a = {a: 42};
Chart.plugins.register(plugin);
var chart = window.acquireChart({
options: {
plugins: {
a: true
}
}
});
spyOn(plugin, 'hook');
Chart.plugins.notify(chart, 'hook');
expect(plugin.hook).toHaveBeenCalled();
expect(plugin.hook.calls.first().args[1]).toEqual({a: 42});
});
it('should call plugins with default options if plugin config options is undefined', function() {
var plugin = {id: 'a', hook: function() {}};
Chart.defaults.global.plugins.a = {a: 'foobar'};
Chart.plugins.register(plugin);
spyOn(plugin, 'hook');
var chart = window.acquireChart();
Chart.plugins.notify(chart, 'hook');
expect(plugin.hook).toHaveBeenCalled();
expect(plugin.hook.calls.first().args[1]).toEqual({a: 'foobar'});
delete Chart.defaults.global.plugins.a;
});
// https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167
it('should invalidate cache when update plugin options', function() {
var plugin = {id: 'a', hook: function() {}};
var chart = window.acquireChart({
plugins: [plugin],
options: {
plugins: {
a: {
foo: 'foo'
}
}
},
});
spyOn(plugin, 'hook');
Chart.plugins.notify(chart, 'hook');
expect(plugin.hook).toHaveBeenCalled();
expect(plugin.hook.calls.first().args[1]).toEqual({foo: 'foo'});
chart.options.plugins.a = {bar: 'bar'};
chart.update();
plugin.hook.calls.reset();
Chart.plugins.notify(chart, 'hook');
expect(plugin.hook).toHaveBeenCalled();
expect(plugin.hook.calls.first().args[1]).toEqual({bar: 'bar'});
});
});
});

View File

@ -0,0 +1,22 @@
describe('Core.scale', function() {
describe('auto', jasmine.specsFromFixtures('core.scale'));
it('should provide default scale label options', function() {
expect(Chart.defaults.scale.scaleLabel).toEqual({
// display property
display: false,
// actual label
labelString: '',
// actual label
lineHeight: 1.2,
// top/bottom padding
padding: {
top: 4,
bottom: 4
}
});
});
});

View File

@ -0,0 +1,29 @@
// Tests of the scale service
describe('Test the scale service', function() {
it('should update scale defaults', function() {
var defaults = {
testProp: true
};
var type = 'my_test_type';
var Constructor = function() {
this.initialized = true;
};
Chart.scaleService.registerScaleType(type, Constructor, defaults);
// Should equal defaults but not be an identical object
expect(Chart.scaleService.getScaleDefaults(type)).toEqual(jasmine.objectContaining({
testProp: true
}));
Chart.scaleService.updateScaleDefaults(type, {
testProp: 'red',
newProp: 42
});
expect(Chart.scaleService.getScaleDefaults(type)).toEqual(jasmine.objectContaining({
testProp: 'red',
newProp: 42
}));
});
});

View File

@ -0,0 +1,96 @@
describe('Test tick generators', function() {
// formatters are used as default config values so users want to be able to reference them
it('Should expose formatters api', function() {
expect(typeof Chart.Ticks).toBeDefined();
expect(typeof Chart.Ticks.formatters).toBeDefined();
expect(typeof Chart.Ticks.formatters.values).toBe('function');
expect(typeof Chart.Ticks.formatters.linear).toBe('function');
expect(typeof Chart.Ticks.formatters.logarithmic).toBe('function');
});
it('Should generate linear spaced ticks with correct precision', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: []
}],
},
options: {
legend: {
display: false,
},
scales: {
xAxes: [{
type: 'linear',
position: 'bottom',
ticks: {
callback: function(value) {
return value.toString();
}
}
}],
yAxes: [{
type: 'linear',
ticks: {
callback: function(value) {
return value.toString();
}
}
}]
}
}
});
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
expect(xAxis.ticks).toEqual(['-1', '-0.8', '-0.6', '-0.4', '-0.2', '0', '0.2', '0.4', '0.6', '0.8', '1']);
expect(yAxis.ticks).toEqual(['1', '0.8', '0.6', '0.4', '0.2', '0', '-0.2', '-0.4', '-0.6', '-0.8', '-1']);
});
it('Should generate logarithmic spaced ticks with correct precision', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
data: []
}],
},
options: {
legend: {
display: false,
},
scales: {
xAxes: [{
type: 'logarithmic',
position: 'bottom',
ticks: {
min: 0.1,
max: 1,
callback: function(value) {
return value.toString();
}
}
}],
yAxes: [{
type: 'logarithmic',
ticks: {
min: 0.1,
max: 1,
callback: function(value) {
return value.toString();
}
}
}]
}
}
});
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
expect(xAxis.ticks).toEqual(['0.1', '0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '1']);
expect(yAxis.ticks).toEqual(['1', '0.9', '0.8', '0.7', '0.6', '0.5', '0.4', '0.3', '0.2', '0.1']);
});
});

View File

@ -0,0 +1,952 @@
// Test the rectangle element
describe('Core.Tooltip', function() {
describe('config', function() {
it('should not include the dataset label in the body string if not defined', function() {
var data = {
datasets: [{
data: [10, 20, 30],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
};
var tooltipItem = {
index: 1,
datasetIndex: 0,
xLabel: 'Point 2',
yLabel: '20'
};
var label = Chart.defaults.global.tooltips.callbacks.label(tooltipItem, data);
expect(label).toBe('20');
data.datasets[0].label = 'My dataset';
label = Chart.defaults.global.tooltips.callbacks.label(tooltipItem, data);
expect(label).toBe('My dataset: 20');
});
});
describe('index mode', function() {
it('Should only use x distance when intersect is false', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
label: 'Dataset 1',
data: [10, 20, 30],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)'
}, {
label: 'Dataset 2',
data: [40, 40, 40],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
},
options: {
tooltips: {
mode: 'index',
intersect: false,
},
hover: {
mode: 'index',
intersect: false
}
}
});
// Trigger an event over top of the
var meta = chart.getDatasetMeta(0);
var point = meta.data[1];
var node = chart.canvas;
var rect = node.getBoundingClientRect();
var evt = new MouseEvent('mousemove', {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point._model.x,
clientY: 0
});
// Manually trigger rather than having an async test
node.dispatchEvent(evt);
// Check and see if tooltip was displayed
var tooltip = chart.tooltip;
var globalDefaults = Chart.defaults.global;
expect(tooltip._view).toEqual(jasmine.objectContaining({
// Positioning
xPadding: 6,
yPadding: 6,
xAlign: 'left',
yAlign: 'center',
// Body
bodyFontColor: '#fff',
_bodyFontFamily: globalDefaults.defaultFontFamily,
_bodyFontStyle: globalDefaults.defaultFontStyle,
_bodyAlign: 'left',
bodyFontSize: globalDefaults.defaultFontSize,
bodySpacing: 2,
// Title
titleFontColor: '#fff',
_titleFontFamily: globalDefaults.defaultFontFamily,
_titleFontStyle: 'bold',
titleFontSize: globalDefaults.defaultFontSize,
_titleAlign: 'left',
titleSpacing: 2,
titleMarginBottom: 6,
// Footer
footerFontColor: '#fff',
_footerFontFamily: globalDefaults.defaultFontFamily,
_footerFontStyle: 'bold',
footerFontSize: globalDefaults.defaultFontSize,
_footerAlign: 'left',
footerSpacing: 2,
footerMarginTop: 6,
// Appearance
caretSize: 5,
cornerRadius: 6,
backgroundColor: 'rgba(0,0,0,0.8)',
opacity: 1,
legendColorBackground: '#fff',
displayColors: true,
// Text
title: ['Point 2'],
beforeBody: [],
body: [{
before: [],
lines: ['Dataset 1: 20'],
after: []
}, {
before: [],
lines: ['Dataset 2: 40'],
after: []
}],
afterBody: [],
footer: [],
caretPadding: 2,
labelColors: [{
borderColor: 'rgb(255, 0, 0)',
backgroundColor: 'rgb(0, 255, 0)'
}, {
borderColor: 'rgb(0, 0, 255)',
backgroundColor: 'rgb(0, 255, 255)'
}]
}));
expect(tooltip._view.x).toBeCloseToPixel(266);
expect(tooltip._view.y).toBeCloseToPixel(155);
});
it('Should only display if intersecting if intersect is set', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
label: 'Dataset 1',
data: [10, 20, 30],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)'
}, {
label: 'Dataset 2',
data: [40, 40, 40],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
},
options: {
tooltips: {
mode: 'index',
intersect: true
}
}
});
// Trigger an event over top of the
var meta = chart.getDatasetMeta(0);
var point = meta.data[1];
var node = chart.canvas;
var rect = node.getBoundingClientRect();
var evt = new MouseEvent('mousemove', {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point._model.x,
clientY: 0
});
// Manually trigger rather than having an async test
node.dispatchEvent(evt);
// Check and see if tooltip was displayed
var tooltip = chart.tooltip;
var globalDefaults = Chart.defaults.global;
expect(tooltip._view).toEqual(jasmine.objectContaining({
// Positioning
xPadding: 6,
yPadding: 6,
// Body
bodyFontColor: '#fff',
_bodyFontFamily: globalDefaults.defaultFontFamily,
_bodyFontStyle: globalDefaults.defaultFontStyle,
_bodyAlign: 'left',
bodyFontSize: globalDefaults.defaultFontSize,
bodySpacing: 2,
// Title
titleFontColor: '#fff',
_titleFontFamily: globalDefaults.defaultFontFamily,
_titleFontStyle: 'bold',
titleFontSize: globalDefaults.defaultFontSize,
_titleAlign: 'left',
titleSpacing: 2,
titleMarginBottom: 6,
// Footer
footerFontColor: '#fff',
_footerFontFamily: globalDefaults.defaultFontFamily,
_footerFontStyle: 'bold',
footerFontSize: globalDefaults.defaultFontSize,
_footerAlign: 'left',
footerSpacing: 2,
footerMarginTop: 6,
// Appearance
caretSize: 5,
cornerRadius: 6,
backgroundColor: 'rgba(0,0,0,0.8)',
opacity: 0,
legendColorBackground: '#fff',
displayColors: true,
}));
});
});
it('Should display in single mode', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
label: 'Dataset 1',
data: [10, 20, 30],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)'
}, {
label: 'Dataset 2',
data: [40, 40, 40],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
},
options: {
tooltips: {
mode: 'single'
}
}
});
// Trigger an event over top of the
var meta = chart.getDatasetMeta(0);
var point = meta.data[1];
var node = chart.canvas;
var rect = node.getBoundingClientRect();
var evt = new MouseEvent('mousemove', {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point._model.x,
clientY: rect.top + point._model.y
});
// Manually trigger rather than having an async test
node.dispatchEvent(evt);
// Check and see if tooltip was displayed
var tooltip = chart.tooltip;
var globalDefaults = Chart.defaults.global;
expect(tooltip._view).toEqual(jasmine.objectContaining({
// Positioning
xPadding: 6,
yPadding: 6,
xAlign: 'left',
yAlign: 'center',
// Body
bodyFontColor: '#fff',
_bodyFontFamily: globalDefaults.defaultFontFamily,
_bodyFontStyle: globalDefaults.defaultFontStyle,
_bodyAlign: 'left',
bodyFontSize: globalDefaults.defaultFontSize,
bodySpacing: 2,
// Title
titleFontColor: '#fff',
_titleFontFamily: globalDefaults.defaultFontFamily,
_titleFontStyle: 'bold',
titleFontSize: globalDefaults.defaultFontSize,
_titleAlign: 'left',
titleSpacing: 2,
titleMarginBottom: 6,
// Footer
footerFontColor: '#fff',
_footerFontFamily: globalDefaults.defaultFontFamily,
_footerFontStyle: 'bold',
footerFontSize: globalDefaults.defaultFontSize,
_footerAlign: 'left',
footerSpacing: 2,
footerMarginTop: 6,
// Appearance
caretSize: 5,
cornerRadius: 6,
backgroundColor: 'rgba(0,0,0,0.8)',
opacity: 1,
legendColorBackground: '#fff',
displayColors: true,
// Text
title: ['Point 2'],
beforeBody: [],
body: [{
before: [],
lines: ['Dataset 1: 20'],
after: []
}],
afterBody: [],
footer: [],
caretPadding: 2,
labelTextColors: ['#fff'],
labelColors: [{
borderColor: 'rgb(255, 0, 0)',
backgroundColor: 'rgb(0, 255, 0)'
}]
}));
expect(tooltip._view.x).toBeCloseToPixel(266);
expect(tooltip._view.y).toBeCloseToPixel(312);
});
it('Should display information from user callbacks', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
label: 'Dataset 1',
data: [10, 20, 30],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)'
}, {
label: 'Dataset 2',
data: [40, 40, 40],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
},
options: {
tooltips: {
mode: 'label',
callbacks: {
beforeTitle: function() {
return 'beforeTitle';
},
title: function() {
return 'title';
},
afterTitle: function() {
return 'afterTitle';
},
beforeBody: function() {
return 'beforeBody';
},
beforeLabel: function() {
return 'beforeLabel';
},
label: function() {
return 'label';
},
afterLabel: function() {
return 'afterLabel';
},
afterBody: function() {
return 'afterBody';
},
beforeFooter: function() {
return 'beforeFooter';
},
footer: function() {
return 'footer';
},
afterFooter: function() {
return 'afterFooter';
},
labelTextColor: function() {
return 'labelTextColor';
}
}
}
}
});
// Trigger an event over top of the
var meta = chart.getDatasetMeta(0);
var point = meta.data[1];
var node = chart.canvas;
var rect = node.getBoundingClientRect();
var evt = new MouseEvent('mousemove', {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point._model.x,
clientY: rect.top + point._model.y
});
// Manually trigger rather than having an async test
node.dispatchEvent(evt);
// Check and see if tooltip was displayed
var tooltip = chart.tooltip;
var globalDefaults = Chart.defaults.global;
expect(tooltip._view).toEqual(jasmine.objectContaining({
// Positioning
xPadding: 6,
yPadding: 6,
xAlign: 'center',
yAlign: 'top',
// Body
bodyFontColor: '#fff',
_bodyFontFamily: globalDefaults.defaultFontFamily,
_bodyFontStyle: globalDefaults.defaultFontStyle,
_bodyAlign: 'left',
bodyFontSize: globalDefaults.defaultFontSize,
bodySpacing: 2,
// Title
titleFontColor: '#fff',
_titleFontFamily: globalDefaults.defaultFontFamily,
_titleFontStyle: 'bold',
titleFontSize: globalDefaults.defaultFontSize,
_titleAlign: 'left',
titleSpacing: 2,
titleMarginBottom: 6,
// Footer
footerFontColor: '#fff',
_footerFontFamily: globalDefaults.defaultFontFamily,
_footerFontStyle: 'bold',
footerFontSize: globalDefaults.defaultFontSize,
_footerAlign: 'left',
footerSpacing: 2,
footerMarginTop: 6,
// Appearance
caretSize: 5,
cornerRadius: 6,
backgroundColor: 'rgba(0,0,0,0.8)',
opacity: 1,
legendColorBackground: '#fff',
// Text
title: ['beforeTitle', 'title', 'afterTitle'],
beforeBody: ['beforeBody'],
body: [{
before: ['beforeLabel'],
lines: ['label'],
after: ['afterLabel']
}, {
before: ['beforeLabel'],
lines: ['label'],
after: ['afterLabel']
}],
afterBody: ['afterBody'],
footer: ['beforeFooter', 'footer', 'afterFooter'],
caretPadding: 2,
labelTextColors: ['labelTextColor', 'labelTextColor'],
labelColors: [{
borderColor: 'rgb(255, 0, 0)',
backgroundColor: 'rgb(0, 255, 0)'
}, {
borderColor: 'rgb(0, 0, 255)',
backgroundColor: 'rgb(0, 255, 255)'
}]
}));
expect(tooltip._view.x).toBeCloseToPixel(214);
expect(tooltip._view.y).toBeCloseToPixel(190);
});
it('Should allow sorting items', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
label: 'Dataset 1',
data: [10, 20, 30],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)'
}, {
label: 'Dataset 2',
data: [40, 40, 40],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
},
options: {
tooltips: {
mode: 'label',
itemSort: function(a, b) {
return a.datasetIndex > b.datasetIndex ? -1 : 1;
}
}
}
});
// Trigger an event over top of the
var meta0 = chart.getDatasetMeta(0);
var point0 = meta0.data[1];
var node = chart.canvas;
var rect = node.getBoundingClientRect();
var evt = new MouseEvent('mousemove', {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point0._model.x,
clientY: rect.top + point0._model.y
});
// Manually trigger rather than having an async test
node.dispatchEvent(evt);
// Check and see if tooltip was displayed
var tooltip = chart.tooltip;
expect(tooltip._view).toEqual(jasmine.objectContaining({
// Positioning
xAlign: 'left',
yAlign: 'center',
// Text
title: ['Point 2'],
beforeBody: [],
body: [{
before: [],
lines: ['Dataset 2: 40'],
after: []
}, {
before: [],
lines: ['Dataset 1: 20'],
after: []
}],
afterBody: [],
footer: [],
labelColors: [{
borderColor: 'rgb(0, 0, 255)',
backgroundColor: 'rgb(0, 255, 255)'
}, {
borderColor: 'rgb(255, 0, 0)',
backgroundColor: 'rgb(0, 255, 0)'
}]
}));
expect(tooltip._view.x).toBeCloseToPixel(266);
expect(tooltip._view.y).toBeCloseToPixel(155);
});
it('should filter items from the tooltip using the callback', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
label: 'Dataset 1',
data: [10, 20, 30],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)',
tooltipHidden: true
}, {
label: 'Dataset 2',
data: [40, 40, 40],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
},
options: {
tooltips: {
mode: 'label',
filter: function(tooltipItem, data) {
// For testing purposes remove the first dataset that has a tooltipHidden property
return !data.datasets[tooltipItem.datasetIndex].tooltipHidden;
}
}
}
});
// Trigger an event over top of the
var meta0 = chart.getDatasetMeta(0);
var point0 = meta0.data[1];
var node = chart.canvas;
var rect = node.getBoundingClientRect();
var evt = new MouseEvent('mousemove', {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point0._model.x,
clientY: rect.top + point0._model.y
});
// Manually trigger rather than having an async test
node.dispatchEvent(evt);
// Check and see if tooltip was displayed
var tooltip = chart.tooltip;
expect(tooltip._view).toEqual(jasmine.objectContaining({
// Positioning
xAlign: 'left',
yAlign: 'center',
// Text
title: ['Point 2'],
beforeBody: [],
body: [{
before: [],
lines: ['Dataset 2: 40'],
after: []
}],
afterBody: [],
footer: [],
labelColors: [{
borderColor: 'rgb(0, 0, 255)',
backgroundColor: 'rgb(0, 255, 255)'
}]
}));
});
it('should set the caretPadding based on a config setting', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
label: 'Dataset 1',
data: [10, 20, 30],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)',
tooltipHidden: true
}, {
label: 'Dataset 2',
data: [40, 40, 40],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
},
options: {
tooltips: {
caretPadding: 10
}
}
});
// Trigger an event over top of the
var meta0 = chart.getDatasetMeta(0);
var point0 = meta0.data[1];
var node = chart.canvas;
var rect = node.getBoundingClientRect();
var evt = new MouseEvent('mousemove', {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point0._model.x,
clientY: rect.top + point0._model.y
});
// Manually trigger rather than having an async test
node.dispatchEvent(evt);
// Check and see if tooltip was displayed
var tooltip = chart.tooltip;
expect(tooltip._model).toEqual(jasmine.objectContaining({
// Positioning
caretPadding: 10,
}));
});
it('Should have dataPoints', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
label: 'Dataset 1',
data: [10, 20, 30],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)'
}, {
label: 'Dataset 2',
data: [40, 40, 40],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
},
options: {
tooltips: {
mode: 'single'
}
}
});
// Trigger an event over top of the
var pointIndex = 1;
var datasetIndex = 0;
var meta = chart.getDatasetMeta(datasetIndex);
var point = meta.data[pointIndex];
var node = chart.canvas;
var rect = node.getBoundingClientRect();
var evt = new MouseEvent('mousemove', {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point._model.x,
clientY: rect.top + point._model.y
});
// Manually trigger rather than having an async test
node.dispatchEvent(evt);
// Check and see if tooltip was displayed
var tooltip = chart.tooltip;
expect(tooltip._view instanceof Object).toBe(true);
expect(tooltip._view.dataPoints instanceof Array).toBe(true);
expect(tooltip._view.dataPoints.length).toEqual(1);
expect(tooltip._view.dataPoints[0].index).toEqual(pointIndex);
expect(tooltip._view.dataPoints[0].datasetIndex).toEqual(datasetIndex);
expect(tooltip._view.dataPoints[0].xLabel).toEqual(
chart.data.labels[pointIndex]
);
expect(tooltip._view.dataPoints[0].yLabel).toEqual(
chart.data.datasets[datasetIndex].data[pointIndex]
);
expect(tooltip._view.dataPoints[0].x).toBeCloseToPixel(point._model.x);
expect(tooltip._view.dataPoints[0].y).toBeCloseToPixel(point._model.y);
});
it('Should not update if active element has not changed', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
label: 'Dataset 1',
data: [10, 20, 30],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)'
}, {
label: 'Dataset 2',
data: [40, 40, 40],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
},
options: {
tooltips: {
mode: 'single',
callbacks: {
title: function() {
return 'registering callback...';
}
}
}
}
});
// Trigger an event over top of the
var meta = chart.getDatasetMeta(0);
var firstPoint = meta.data[1];
var node = chart.chart.canvas;
var rect = node.getBoundingClientRect();
var firstEvent = new MouseEvent('mousemove', {
view: window,
bubbles: false,
cancelable: true,
clientX: rect.left + firstPoint._model.x,
clientY: rect.top + firstPoint._model.y
});
var tooltip = chart.tooltip;
spyOn(tooltip, 'update');
/* Manually trigger rather than having an async test */
// First dispatch change event, should update tooltip
node.dispatchEvent(firstEvent);
expect(tooltip.update).toHaveBeenCalledWith(true);
// Reset calls
tooltip.update.calls.reset();
// Second dispatch change event (same event), should not update tooltip
node.dispatchEvent(firstEvent);
expect(tooltip.update).not.toHaveBeenCalled();
});
describe('positioners', function() {
it('Should call custom positioner with correct parameters and scope', function() {
Chart.Tooltip.positioners.test = function() {
return {x: 0, y: 0};
};
spyOn(Chart.Tooltip.positioners, 'test').and.callThrough();
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
label: 'Dataset 1',
data: [10, 20, 30],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)'
}, {
label: 'Dataset 2',
data: [40, 40, 40],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
},
options: {
tooltips: {
mode: 'nearest',
position: 'test'
}
}
});
// Trigger an event over top of the
var pointIndex = 1;
var datasetIndex = 0;
var meta = chart.getDatasetMeta(datasetIndex);
var point = meta.data[pointIndex];
var node = chart.canvas;
var rect = node.getBoundingClientRect();
var evt = new MouseEvent('mousemove', {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point._model.x,
clientY: rect.top + point._model.y
});
// Manually trigger rather than having an async test
node.dispatchEvent(evt);
var fn = Chart.Tooltip.positioners.test;
expect(fn.calls.count()).toBe(1);
expect(fn.calls.first().args[0] instanceof Array).toBe(true);
expect(fn.calls.first().args[1].hasOwnProperty('x')).toBe(true);
expect(fn.calls.first().args[1].hasOwnProperty('y')).toBe(true);
expect(fn.calls.first().object instanceof Chart.Tooltip).toBe(true);
});
});
it('Should avoid tooltip truncation in x axis if there is enough space to show tooltip without truncation', function() {
var chart = window.acquireChart({
type: 'pie',
data: {
datasets: [{
data: [
50,
50
],
backgroundColor: [
'rgb(255, 0, 0)',
'rgb(0, 255, 0)'
],
label: 'Dataset 1'
}],
labels: [
'Red long tooltip text to avoid unnecessary loop steps',
'Green long tooltip text to avoid unnecessary loop steps'
]
},
options: {
responsive: true,
animation: {
// without this slice center point is calculated wrong
animateRotate: false
}
}
});
// Trigger an event over top of the slice
for (var slice = 0; slice < 2; slice++) {
var meta = chart.getDatasetMeta(0);
var point = meta.data[slice].getCenterPoint();
var tooltipPosition = meta.data[slice].tooltipPosition();
var node = chart.canvas;
var rect = node.getBoundingClientRect();
var mouseMoveEvent = new MouseEvent('mousemove', {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point.x,
clientY: rect.top + point.y
});
var mouseOutEvent = new MouseEvent('mouseout');
// Lets cycle while tooltip is narrower than chart area
var infiniteCycleDefense = 70;
for (var i = 0; i < infiniteCycleDefense; i++) {
chart.config.data.labels[slice] = chart.config.data.labels[slice] + 'l';
chart.update();
node.dispatchEvent(mouseOutEvent);
node.dispatchEvent(mouseMoveEvent);
var model = chart.tooltip._model;
expect(model.x).toBeGreaterThanOrEqual(0);
if (model.width <= chart.width) {
expect(model.x + model.width).toBeLessThanOrEqual(chart.width);
}
expect(model.caretX).toBe(tooltipPosition.x);
// if tooltip is longer than chart area then all tests done
if (model.width > chart.width) {
break;
}
}
}
});
});

View File

@ -0,0 +1,216 @@
// Test the rectangle element
describe('Arc element tests', function() {
it ('Should be constructed', function() {
var arc = new Chart.elements.Arc({
_datasetIndex: 2,
_index: 1
});
expect(arc).not.toBe(undefined);
expect(arc._datasetIndex).toBe(2);
expect(arc._index).toBe(1);
});
it ('should determine if in range', function() {
var arc = new Chart.elements.Arc({
_datasetIndex: 2,
_index: 1
});
// Make sure we can run these before the view is added
expect(arc.inRange(2, 2)).toBe(false);
expect(arc.inLabelRange(2)).toBe(false);
// Mock out the view as if the controller put it there
arc._view = {
startAngle: 0,
endAngle: Math.PI / 2,
x: 0,
y: 0,
innerRadius: 5,
outerRadius: 10,
};
expect(arc.inRange(2, 2)).toBe(false);
expect(arc.inRange(7, 0)).toBe(true);
expect(arc.inRange(0, 11)).toBe(false);
expect(arc.inRange(Math.sqrt(32), Math.sqrt(32))).toBe(true);
expect(arc.inRange(-1.0 * Math.sqrt(7), Math.sqrt(7))).toBe(false);
});
it ('should get the tooltip position', function() {
var arc = new Chart.elements.Arc({
_datasetIndex: 2,
_index: 1
});
// Mock out the view as if the controller put it there
arc._view = {
startAngle: 0,
endAngle: Math.PI / 2,
x: 0,
y: 0,
innerRadius: 0,
outerRadius: Math.sqrt(2),
};
var pos = arc.tooltipPosition();
expect(pos.x).toBeCloseTo(0.5);
expect(pos.y).toBeCloseTo(0.5);
});
it ('should get the area', function() {
var arc = new Chart.elements.Arc({
_datasetIndex: 2,
_index: 1
});
// Mock out the view as if the controller put it there
arc._view = {
startAngle: 0,
endAngle: Math.PI / 2,
x: 0,
y: 0,
innerRadius: 0,
outerRadius: Math.sqrt(2),
};
expect(arc.getArea()).toBeCloseTo(0.5 * Math.PI, 6);
});
it ('should get the center', function() {
var arc = new Chart.elements.Arc({
_datasetIndex: 2,
_index: 1
});
// Mock out the view as if the controller put it there
arc._view = {
startAngle: 0,
endAngle: Math.PI / 2,
x: 0,
y: 0,
innerRadius: 0,
outerRadius: Math.sqrt(2),
};
var center = arc.getCenterPoint();
expect(center.x).toBeCloseTo(0.5, 6);
expect(center.y).toBeCloseTo(0.5, 6);
});
it ('should draw correctly with no border', function() {
var mockContext = window.createMockContext();
var arc = new Chart.elements.Arc({
_datasetIndex: 2,
_index: 1,
_chart: {
ctx: mockContext,
}
});
// Mock out the view as if the controller put it there
arc._view = {
startAngle: 0,
endAngle: Math.PI / 2,
x: 10,
y: 5,
innerRadius: 1,
outerRadius: 3,
backgroundColor: 'rgb(0, 0, 255)',
borderColor: 'rgb(255, 0, 0)',
};
arc.draw();
expect(mockContext.getCalls()).toEqual([{
name: 'beginPath',
args: []
}, {
name: 'arc',
args: [10, 5, 3, 0, Math.PI / 2]
}, {
name: 'arc',
args: [10, 5, 1, Math.PI / 2, 0, true]
}, {
name: 'closePath',
args: []
}, {
name: 'setStrokeStyle',
args: ['rgb(255, 0, 0)']
}, {
name: 'setLineWidth',
args: [undefined]
}, {
name: 'setFillStyle',
args: ['rgb(0, 0, 255)']
}, {
name: 'fill',
args: []
}, {
name: 'setLineJoin',
args: ['bevel']
}]);
});
it ('should draw correctly with a border', function() {
var mockContext = window.createMockContext();
var arc = new Chart.elements.Arc({
_datasetIndex: 2,
_index: 1,
_chart: {
ctx: mockContext,
}
});
// Mock out the view as if the controller put it there
arc._view = {
startAngle: 0,
endAngle: Math.PI / 2,
x: 10,
y: 5,
innerRadius: 1,
outerRadius: 3,
backgroundColor: 'rgb(0, 0, 255)',
borderColor: 'rgb(255, 0, 0)',
borderWidth: 5
};
arc.draw();
expect(mockContext.getCalls()).toEqual([{
name: 'beginPath',
args: []
}, {
name: 'arc',
args: [10, 5, 3, 0, Math.PI / 2]
}, {
name: 'arc',
args: [10, 5, 1, Math.PI / 2, 0, true]
}, {
name: 'closePath',
args: []
}, {
name: 'setStrokeStyle',
args: ['rgb(255, 0, 0)']
}, {
name: 'setLineWidth',
args: [5]
}, {
name: 'setFillStyle',
args: ['rgb(0, 0, 255)']
}, {
name: 'fill',
args: []
}, {
name: 'setLineJoin',
args: ['bevel']
}, {
name: 'stroke',
args: []
}]);
});
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,528 @@
// Test the point element
describe('Point element tests', function() {
it ('Should be constructed', function() {
var point = new Chart.elements.Point({
_datasetIndex: 2,
_index: 1
});
expect(point).not.toBe(undefined);
expect(point._datasetIndex).toBe(2);
expect(point._index).toBe(1);
});
it ('Should correctly identify as in range', function() {
var point = new Chart.elements.Point({
_datasetIndex: 2,
_index: 1
});
// Safely handles if these are called before the viewmodel is instantiated
expect(point.inRange(5)).toBe(false);
expect(point.inLabelRange(5)).toBe(false);
// Attach a view object as if we were the controller
point._view = {
radius: 2,
hitRadius: 3,
x: 10,
y: 15
};
expect(point.inRange(10, 15)).toBe(true);
expect(point.inRange(10, 10)).toBe(false);
expect(point.inRange(10, 5)).toBe(false);
expect(point.inRange(5, 5)).toBe(false);
expect(point.inLabelRange(5)).toBe(false);
expect(point.inLabelRange(7)).toBe(true);
expect(point.inLabelRange(10)).toBe(true);
expect(point.inLabelRange(12)).toBe(true);
expect(point.inLabelRange(15)).toBe(false);
expect(point.inLabelRange(20)).toBe(false);
});
it ('should get the correct tooltip position', function() {
var point = new Chart.elements.Point({
_datasetIndex: 2,
_index: 1
});
// Attach a view object as if we were the controller
point._view = {
radius: 2,
borderWidth: 6,
x: 10,
y: 15
};
expect(point.tooltipPosition()).toEqual({
x: 10,
y: 15,
padding: 8
});
});
it('should get the correct area', function() {
var point = new Chart.elements.Point({
_datasetIndex: 2,
_index: 1
});
// Attach a view object as if we were the controller
point._view = {
radius: 2,
};
expect(point.getArea()).toEqual(Math.PI * 4);
});
it('should get the correct center point', function() {
var point = new Chart.elements.Point({
_datasetIndex: 2,
_index: 1
});
// Attach a view object as if we were the controller
point._view = {
radius: 2,
x: 10,
y: 10
};
expect(point.getCenterPoint()).toEqual({x: 10, y: 10});
});
it ('should draw correctly', function() {
var mockContext = window.createMockContext();
var point = new Chart.elements.Point({
_datasetIndex: 2,
_index: 1,
_chart: {
ctx: mockContext,
}
});
// Attach a view object as if we were the controller
point._view = {
radius: 2,
pointStyle: 'circle',
hitRadius: 3,
borderColor: 'rgba(1, 2, 3, 1)',
borderWidth: 6,
backgroundColor: 'rgba(0, 255, 0)',
x: 10,
y: 15,
ctx: mockContext
};
point.draw();
expect(mockContext.getCalls()).toEqual([{
name: 'setStrokeStyle',
args: ['rgba(1, 2, 3, 1)']
}, {
name: 'setLineWidth',
args: [6]
}, {
name: 'setFillStyle',
args: ['rgba(0, 255, 0)']
}, {
name: 'beginPath',
args: []
}, {
name: 'arc',
args: [10, 15, 2, 0, 2 * Math.PI]
}, {
name: 'closePath',
args: [],
}, {
name: 'fill',
args: [],
}, {
name: 'stroke',
args: []
}]);
mockContext.resetCalls();
point._view.pointStyle = 'triangle';
point.draw();
expect(mockContext.getCalls()).toEqual([{
name: 'setStrokeStyle',
args: ['rgba(1, 2, 3, 1)']
}, {
name: 'setLineWidth',
args: [6]
}, {
name: 'setFillStyle',
args: ['rgba(0, 255, 0)']
}, {
name: 'beginPath',
args: []
}, {
name: 'moveTo',
args: [10 - 3 * 2 / Math.sqrt(3) / 2, 15 + 3 * 2 / Math.sqrt(3) * Math.sqrt(3) / 2 / 3]
}, {
name: 'lineTo',
args: [10 + 3 * 2 / Math.sqrt(3) / 2, 15 + 3 * 2 / Math.sqrt(3) * Math.sqrt(3) / 2 / 3],
}, {
name: 'lineTo',
args: [10, 15 - 2 * 3 * 2 / Math.sqrt(3) * Math.sqrt(3) / 2 / 3],
}, {
name: 'closePath',
args: [],
}, {
name: 'fill',
args: [],
}, {
name: 'stroke',
args: []
}]);
mockContext.resetCalls();
point._view.pointStyle = 'rect';
point.draw();
expect(mockContext.getCalls()).toEqual([{
name: 'setStrokeStyle',
args: ['rgba(1, 2, 3, 1)']
}, {
name: 'setLineWidth',
args: [6]
}, {
name: 'setFillStyle',
args: ['rgba(0, 255, 0)']
}, {
name: 'beginPath',
args: []
}, {
name: 'fillRect',
args: [10 - 1 / Math.SQRT2 * 2, 15 - 1 / Math.SQRT2 * 2, 2 / Math.SQRT2 * 2, 2 / Math.SQRT2 * 2]
}, {
name: 'strokeRect',
args: [10 - 1 / Math.SQRT2 * 2, 15 - 1 / Math.SQRT2 * 2, 2 / Math.SQRT2 * 2, 2 / Math.SQRT2 * 2]
}, {
name: 'stroke',
args: []
}]);
var drawRoundedRectangleSpy = jasmine.createSpy('drawRoundedRectangle');
var drawRoundedRectangle = Chart.helpers.canvas.roundedRect;
var offset = point._view.radius / Math.SQRT2;
Chart.helpers.canvas.roundedRect = drawRoundedRectangleSpy;
mockContext.resetCalls();
point._view.pointStyle = 'rectRounded';
point.draw();
expect(drawRoundedRectangleSpy).toHaveBeenCalledWith(
mockContext,
10 - offset,
15 - offset,
Math.SQRT2 * 2,
Math.SQRT2 * 2,
2 / 2
);
expect(mockContext.getCalls()).toContain(
jasmine.objectContaining({
name: 'fill',
args: [],
})
);
Chart.helpers.canvas.roundedRect = drawRoundedRectangle;
mockContext.resetCalls();
point._view.pointStyle = 'rectRot';
point.draw();
expect(mockContext.getCalls()).toEqual([{
name: 'setStrokeStyle',
args: ['rgba(1, 2, 3, 1)']
}, {
name: 'setLineWidth',
args: [6]
}, {
name: 'setFillStyle',
args: ['rgba(0, 255, 0)']
}, {
name: 'beginPath',
args: []
}, {
name: 'moveTo',
args: [10 - 1 / Math.SQRT2 * 2, 15]
}, {
name: 'lineTo',
args: [10, 15 + 1 / Math.SQRT2 * 2]
}, {
name: 'lineTo',
args: [10 + 1 / Math.SQRT2 * 2, 15],
}, {
name: 'lineTo',
args: [10, 15 - 1 / Math.SQRT2 * 2],
}, {
name: 'closePath',
args: []
}, {
name: 'fill',
args: [],
}, {
name: 'stroke',
args: []
}]);
mockContext.resetCalls();
point._view.pointStyle = 'cross';
point.draw();
expect(mockContext.getCalls()).toEqual([{
name: 'setStrokeStyle',
args: ['rgba(1, 2, 3, 1)']
}, {
name: 'setLineWidth',
args: [6]
}, {
name: 'setFillStyle',
args: ['rgba(0, 255, 0)']
}, {
name: 'beginPath',
args: []
}, {
name: 'moveTo',
args: [10, 17]
}, {
name: 'lineTo',
args: [10, 13],
}, {
name: 'moveTo',
args: [8, 15],
}, {
name: 'lineTo',
args: [12, 15],
}, {
name: 'closePath',
args: [],
}, {
name: 'stroke',
args: []
}]);
mockContext.resetCalls();
point._view.pointStyle = 'crossRot';
point.draw();
expect(mockContext.getCalls()).toEqual([{
name: 'setStrokeStyle',
args: ['rgba(1, 2, 3, 1)']
}, {
name: 'setLineWidth',
args: [6]
}, {
name: 'setFillStyle',
args: ['rgba(0, 255, 0)']
}, {
name: 'beginPath',
args: []
}, {
name: 'moveTo',
args: [10 - Math.cos(Math.PI / 4) * 2, 15 - Math.sin(Math.PI / 4) * 2]
}, {
name: 'lineTo',
args: [10 + Math.cos(Math.PI / 4) * 2, 15 + Math.sin(Math.PI / 4) * 2],
}, {
name: 'moveTo',
args: [10 - Math.cos(Math.PI / 4) * 2, 15 + Math.sin(Math.PI / 4) * 2],
}, {
name: 'lineTo',
args: [10 + Math.cos(Math.PI / 4) * 2, 15 - Math.sin(Math.PI / 4) * 2],
}, {
name: 'closePath',
args: [],
}, {
name: 'stroke',
args: []
}]);
mockContext.resetCalls();
point._view.pointStyle = 'star';
point.draw();
expect(mockContext.getCalls()).toEqual([{
name: 'setStrokeStyle',
args: ['rgba(1, 2, 3, 1)']
}, {
name: 'setLineWidth',
args: [6]
}, {
name: 'setFillStyle',
args: ['rgba(0, 255, 0)']
}, {
name: 'beginPath',
args: []
}, {
name: 'moveTo',
args: [10, 17]
}, {
name: 'lineTo',
args: [10, 13],
}, {
name: 'moveTo',
args: [8, 15],
}, {
name: 'lineTo',
args: [12, 15],
}, {
name: 'moveTo',
args: [10 - Math.cos(Math.PI / 4) * 2, 15 - Math.sin(Math.PI / 4) * 2]
}, {
name: 'lineTo',
args: [10 + Math.cos(Math.PI / 4) * 2, 15 + Math.sin(Math.PI / 4) * 2],
}, {
name: 'moveTo',
args: [10 - Math.cos(Math.PI / 4) * 2, 15 + Math.sin(Math.PI / 4) * 2],
}, {
name: 'lineTo',
args: [10 + Math.cos(Math.PI / 4) * 2, 15 - Math.sin(Math.PI / 4) * 2],
}, {
name: 'closePath',
args: [],
}, {
name: 'stroke',
args: []
}]);
mockContext.resetCalls();
point._view.pointStyle = 'line';
point.draw();
expect(mockContext.getCalls()).toEqual([{
name: 'setStrokeStyle',
args: ['rgba(1, 2, 3, 1)']
}, {
name: 'setLineWidth',
args: [6]
}, {
name: 'setFillStyle',
args: ['rgba(0, 255, 0)']
}, {
name: 'beginPath',
args: []
}, {
name: 'moveTo',
args: [8, 15]
}, {
name: 'lineTo',
args: [12, 15],
}, {
name: 'closePath',
args: [],
}, {
name: 'stroke',
args: []
}]);
mockContext.resetCalls();
point._view.pointStyle = 'dash';
point.draw();
expect(mockContext.getCalls()).toEqual([{
name: 'setStrokeStyle',
args: ['rgba(1, 2, 3, 1)']
}, {
name: 'setLineWidth',
args: [6]
}, {
name: 'setFillStyle',
args: ['rgba(0, 255, 0)']
}, {
name: 'beginPath',
args: []
}, {
name: 'moveTo',
args: [10, 15]
}, {
name: 'lineTo',
args: [12, 15],
}, {
name: 'closePath',
args: [],
}, {
name: 'stroke',
args: []
}]);
});
it ('should draw correctly with default settings if necessary', function() {
var mockContext = window.createMockContext();
var point = new Chart.elements.Point({
_datasetIndex: 2,
_index: 1,
_chart: {
ctx: mockContext,
}
});
// Attach a view object as if we were the controller
point._view = {
radius: 2,
hitRadius: 3,
x: 10,
y: 15,
ctx: mockContext
};
point.draw();
expect(mockContext.getCalls()).toEqual([{
name: 'setStrokeStyle',
args: ['rgba(0,0,0,0.1)']
}, {
name: 'setLineWidth',
args: [1]
}, {
name: 'setFillStyle',
args: ['rgba(0,0,0,0.1)']
}, {
name: 'beginPath',
args: []
}, {
name: 'arc',
args: [10, 15, 2, 0, 2 * Math.PI]
}, {
name: 'closePath',
args: [],
}, {
name: 'fill',
args: [],
}, {
name: 'stroke',
args: []
}]);
});
it ('should not draw if skipped', function() {
var mockContext = window.createMockContext();
var point = new Chart.elements.Point({
_datasetIndex: 2,
_index: 1,
_chart: {
ctx: mockContext,
}
});
// Attach a view object as if we were the controller
point._view = {
radius: 2,
hitRadius: 3,
x: 10,
y: 15,
ctx: mockContext,
skip: true
};
point.draw();
expect(mockContext.getCalls()).toEqual([]);
});
});

View File

@ -0,0 +1,337 @@
// Test the rectangle element
describe('Rectangle element tests', function() {
it ('Should be constructed', function() {
var rectangle = new Chart.elements.Rectangle({
_datasetIndex: 2,
_index: 1
});
expect(rectangle).not.toBe(undefined);
expect(rectangle._datasetIndex).toBe(2);
expect(rectangle._index).toBe(1);
});
it ('Should correctly identify as in range', function() {
var rectangle = new Chart.elements.Rectangle({
_datasetIndex: 2,
_index: 1
});
// Safely handles if these are called before the viewmodel is instantiated
expect(rectangle.inRange(5)).toBe(false);
expect(rectangle.inLabelRange(5)).toBe(false);
// Attach a view object as if we were the controller
rectangle._view = {
base: 0,
width: 4,
x: 10,
y: 15
};
expect(rectangle.inRange(10, 15)).toBe(true);
expect(rectangle.inRange(10, 10)).toBe(true);
expect(rectangle.inRange(10, 16)).toBe(false);
expect(rectangle.inRange(5, 5)).toBe(false);
expect(rectangle.inLabelRange(5)).toBe(false);
expect(rectangle.inLabelRange(7)).toBe(false);
expect(rectangle.inLabelRange(10)).toBe(true);
expect(rectangle.inLabelRange(12)).toBe(true);
expect(rectangle.inLabelRange(15)).toBe(false);
expect(rectangle.inLabelRange(20)).toBe(false);
// Test when the y is below the base (negative bar)
var negativeRectangle = new Chart.elements.Rectangle({
_datasetIndex: 2,
_index: 1
});
// Attach a view object as if we were the controller
negativeRectangle._view = {
base: 0,
width: 4,
x: 10,
y: -15
};
expect(negativeRectangle.inRange(10, -16)).toBe(false);
expect(negativeRectangle.inRange(10, 1)).toBe(false);
expect(negativeRectangle.inRange(10, -5)).toBe(true);
});
it ('should get the correct height', function() {
var rectangle = new Chart.elements.Rectangle({
_datasetIndex: 2,
_index: 1
});
// Attach a view object as if we were the controller
rectangle._view = {
base: 0,
width: 4,
x: 10,
y: 15
};
expect(rectangle.height()).toBe(-15);
// Test when the y is below the base (negative bar)
var negativeRectangle = new Chart.elements.Rectangle({
_datasetIndex: 2,
_index: 1
});
// Attach a view object as if we were the controller
negativeRectangle._view = {
base: -10,
width: 4,
x: 10,
y: -15
};
expect(negativeRectangle.height()).toBe(5);
});
it ('should get the correct tooltip position', function() {
var rectangle = new Chart.elements.Rectangle({
_datasetIndex: 2,
_index: 1
});
// Attach a view object as if we were the controller
rectangle._view = {
base: 0,
width: 4,
x: 10,
y: 15
};
expect(rectangle.tooltipPosition()).toEqual({
x: 10,
y: 15,
});
// Test when the y is below the base (negative bar)
var negativeRectangle = new Chart.elements.Rectangle({
_datasetIndex: 2,
_index: 1
});
// Attach a view object as if we were the controller
negativeRectangle._view = {
base: -10,
width: 4,
x: 10,
y: -15
};
expect(negativeRectangle.tooltipPosition()).toEqual({
x: 10,
y: -15,
});
});
it ('should get the correct area', function() {
var rectangle = new Chart.elements.Rectangle({
_datasetIndex: 2,
_index: 1
});
// Attach a view object as if we were the controller
rectangle._view = {
base: 0,
width: 4,
x: 10,
y: 15
};
expect(rectangle.getArea()).toEqual(60);
});
it ('should get the center', function() {
var rectangle = new Chart.elements.Rectangle({
_datasetIndex: 2,
_index: 1
});
// Attach a view object as if we were the controller
rectangle._view = {
base: 0,
width: 4,
x: 10,
y: 15
};
expect(rectangle.getCenterPoint()).toEqual({x: 10, y: 7.5});
});
it ('should draw correctly', function() {
var mockContext = window.createMockContext();
var rectangle = new Chart.elements.Rectangle({
_datasetIndex: 2,
_index: 1,
_chart: {
ctx: mockContext,
}
});
// Attach a view object as if we were the controller
rectangle._view = {
backgroundColor: 'rgb(255, 0, 0)',
base: 0,
borderColor: 'rgb(0, 0, 255)',
borderWidth: 1,
ctx: mockContext,
width: 4,
x: 10,
y: 15,
};
rectangle.draw();
expect(mockContext.getCalls()).toEqual([{
name: 'beginPath',
args: [],
}, {
name: 'setFillStyle',
args: ['rgb(255, 0, 0)']
}, {
name: 'setStrokeStyle',
args: ['rgb(0, 0, 255)'],
}, {
name: 'setLineWidth',
args: [1]
}, {
name: 'moveTo',
args: [8.5, 0]
}, {
name: 'lineTo',
args: [8.5, 14.5] // This is a minus bar. Not 15.5
}, {
name: 'lineTo',
args: [11.5, 14.5]
}, {
name: 'lineTo',
args: [11.5, 0]
}, {
name: 'fill',
args: [],
}, {
name: 'stroke',
args: []
}]);
});
it ('should draw correctly with no stroke', function() {
var mockContext = window.createMockContext();
var rectangle = new Chart.elements.Rectangle({
_datasetIndex: 2,
_index: 1,
_chart: {
ctx: mockContext,
}
});
// Attach a view object as if we were the controller
rectangle._view = {
backgroundColor: 'rgb(255, 0, 0)',
base: 0,
borderColor: 'rgb(0, 0, 255)',
ctx: mockContext,
width: 4,
x: 10,
y: 15,
};
rectangle.draw();
expect(mockContext.getCalls()).toEqual([{
name: 'beginPath',
args: [],
}, {
name: 'setFillStyle',
args: ['rgb(255, 0, 0)']
}, {
name: 'setStrokeStyle',
args: ['rgb(0, 0, 255)'],
}, {
name: 'setLineWidth',
args: [undefined]
}, {
name: 'moveTo',
args: [8, 0]
}, {
name: 'lineTo',
args: [8, 15]
}, {
name: 'lineTo',
args: [12, 15]
}, {
name: 'lineTo',
args: [12, 0]
}, {
name: 'fill',
args: [],
}]);
});
function testBorderSkipped(borderSkipped, expectedDrawCalls) {
var mockContext = window.createMockContext();
var rectangle = new Chart.elements.Rectangle({
_chart: {ctx: mockContext}
});
// Attach a view object as if we were the controller
rectangle._view = {
borderSkipped: borderSkipped, // set tested 'borderSkipped' parameter
ctx: mockContext,
base: 0,
width: 4,
x: 10,
y: 15,
};
rectangle.draw();
var drawCalls = rectangle._view.ctx.getCalls().splice(4, 4);
expect(drawCalls).toEqual(expectedDrawCalls);
}
it ('should draw correctly respecting "borderSkipped" == "bottom"', function() {
testBorderSkipped ('bottom', [
{name: 'moveTo', args: [8, 0]},
{name: 'lineTo', args: [8, 15]},
{name: 'lineTo', args: [12, 15]},
{name: 'lineTo', args: [12, 0]},
]);
});
it ('should draw correctly respecting "borderSkipped" == "left"', function() {
testBorderSkipped ('left', [
{name: 'moveTo', args: [8, 15]},
{name: 'lineTo', args: [12, 15]},
{name: 'lineTo', args: [12, 0]},
{name: 'lineTo', args: [8, 0]},
]);
});
it ('should draw correctly respecting "borderSkipped" == "top"', function() {
testBorderSkipped ('top', [
{name: 'moveTo', args: [12, 15]},
{name: 'lineTo', args: [12, 0]},
{name: 'lineTo', args: [8, 0]},
{name: 'lineTo', args: [8, 15]},
]);
});
it ('should draw correctly respecting "borderSkipped" == "right"', function() {
testBorderSkipped ('right', [
{name: 'moveTo', args: [12, 0]},
{name: 'lineTo', args: [8, 0]},
{name: 'lineTo', args: [8, 15]},
{name: 'lineTo', args: [12, 15]},
]);
});
});

View File

@ -0,0 +1,296 @@
// Test the bubble chart default config
describe('Default Configs', function() {
describe('Bubble Chart', function() {
it('should return correct tooltip strings', function() {
var config = Chart.defaults.bubble;
var chart = window.acquireChart({
type: 'bubble',
data: {
datasets: [{
label: 'My dataset',
data: [{
x: 10,
y: 12,
r: 5
}]
}]
},
options: config
});
// fake out the tooltip hover and force the tooltip to update
chart.tooltip._active = [chart.getDatasetMeta(0).data[0]];
chart.tooltip.update();
// Title is always blank
expect(chart.tooltip._model.title).toEqual([]);
expect(chart.tooltip._model.body).toEqual([{
before: [],
lines: ['My dataset: (10, 12, 5)'],
after: []
}]);
});
});
describe('Doughnut Chart', function() {
it('should return correct tooltip strings', function() {
var config = Chart.defaults.doughnut;
var chart = window.acquireChart({
type: 'doughnut',
data: {
labels: ['label1', 'label2', 'label3'],
datasets: [{
data: [10, 20, 30],
}]
},
options: config
});
// fake out the tooltip hover and force the tooltip to update
chart.tooltip._active = [chart.getDatasetMeta(0).data[1]];
chart.tooltip.update();
// Title is always blank
expect(chart.tooltip._model.title).toEqual([]);
expect(chart.tooltip._model.body).toEqual([{
before: [],
lines: ['label2: 20'],
after: []
}]);
});
it('should return correct tooltip string for a multiline label', function() {
var config = Chart.defaults.doughnut;
var chart = window.acquireChart({
type: 'doughnut',
data: {
labels: ['label1', ['row1', 'row2', 'row3'], 'label3'],
datasets: [{
data: [10, 20, 30],
}]
},
options: config
});
// fake out the tooltip hover and force the tooltip to update
chart.tooltip._active = [chart.getDatasetMeta(0).data[1]];
chart.tooltip.update();
// Title is always blank
expect(chart.tooltip._model.title).toEqual([]);
expect(chart.tooltip._model.body).toEqual([{
before: [],
lines: [
'row1: 20',
'row2',
'row3'
],
after: []
}]);
});
it('should return the correct html legend', function() {
var config = Chart.defaults.doughnut;
var chart = window.acquireChart({
type: 'doughnut',
data: {
labels: ['label1', 'label2'],
datasets: [{
data: [10, 20],
backgroundColor: ['red', 'green']
}]
},
options: config
});
var expectedLegend = '<ul class="' + chart.id + '-legend"><li><span style="background-color:red"></span>label1</li><li><span style="background-color:green"></span>label2</li></ul>';
expect(chart.generateLegend()).toBe(expectedLegend);
});
it('should return correct legend label objects', function() {
var config = Chart.defaults.doughnut;
var chart = window.acquireChart({
type: 'doughnut',
data: {
labels: ['label1', 'label2', 'label3'],
datasets: [{
data: [10, 20, NaN],
backgroundColor: ['red', 'green', 'blue'],
borderWidth: 2,
borderColor: '#000'
}]
},
options: config
});
var expected = [{
text: 'label1',
fillStyle: 'red',
hidden: false,
index: 0,
strokeStyle: '#000',
lineWidth: 2
}, {
text: 'label2',
fillStyle: 'green',
hidden: false,
index: 1,
strokeStyle: '#000',
lineWidth: 2
}, {
text: 'label3',
fillStyle: 'blue',
hidden: true,
index: 2,
strokeStyle: '#000',
lineWidth: 2
}];
expect(chart.legend.legendItems).toEqual(expected);
});
it('should hide the correct arc when a legend item is clicked', function() {
var config = Chart.defaults.doughnut;
var chart = window.acquireChart({
type: 'doughnut',
data: {
labels: ['label1', 'label2', 'label3'],
datasets: [{
data: [10, 20, NaN],
backgroundColor: ['red', 'green', 'blue'],
borderWidth: 2,
borderColor: '#000'
}]
},
options: config
});
var meta = chart.getDatasetMeta(0);
spyOn(chart, 'update').and.callThrough();
var legendItem = chart.legend.legendItems[0];
config.legend.onClick.call(chart.legend, null, legendItem);
expect(meta.data[0].hidden).toBe(true);
expect(chart.update).toHaveBeenCalled();
config.legend.onClick.call(chart.legend, null, legendItem);
expect(meta.data[0].hidden).toBe(false);
});
});
describe('Polar Area Chart', function() {
it('should return correct tooltip strings', function() {
var config = Chart.defaults.polarArea;
var chart = window.acquireChart({
type: 'polarArea',
data: {
labels: ['label1', 'label2', 'label3'],
datasets: [{
data: [10, 20, 30],
}]
},
options: config
});
// fake out the tooltip hover and force the tooltip to update
chart.tooltip._active = [chart.getDatasetMeta(0).data[1]];
chart.tooltip.update();
// Title is always blank
expect(chart.tooltip._model.title).toEqual([]);
expect(chart.tooltip._model.body).toEqual([{
before: [],
lines: ['label2: 20'],
after: []
}]);
});
it('should return the correct html legend', function() {
var config = Chart.defaults.polarArea;
var chart = window.acquireChart({
type: 'polarArea',
data: {
labels: ['label1', 'label2'],
datasets: [{
data: [10, 20],
backgroundColor: ['red', 'green']
}]
},
options: config
});
var expectedLegend = '<ul class="' + chart.id + '-legend"><li><span style="background-color:red"></span>label1</li><li><span style="background-color:green"></span>label2</li></ul>';
expect(chart.generateLegend()).toBe(expectedLegend);
});
it('should return correct legend label objects', function() {
var config = Chart.defaults.polarArea;
var chart = window.acquireChart({
type: 'polarArea',
data: {
labels: ['label1', 'label2', 'label3'],
datasets: [{
data: [10, 20, NaN],
backgroundColor: ['red', 'green', 'blue'],
borderWidth: 2,
borderColor: '#000'
}]
},
options: config
});
var expected = [{
text: 'label1',
fillStyle: 'red',
hidden: false,
index: 0,
strokeStyle: '#000',
lineWidth: 2
}, {
text: 'label2',
fillStyle: 'green',
hidden: false,
index: 1,
strokeStyle: '#000',
lineWidth: 2
}, {
text: 'label3',
fillStyle: 'blue',
hidden: true,
index: 2,
strokeStyle: '#000',
lineWidth: 2
}];
expect(chart.legend.legendItems).toEqual(expected);
});
it('should hide the correct arc when a legend item is clicked', function() {
var config = Chart.defaults.polarArea;
var chart = window.acquireChart({
type: 'polarArea',
data: {
labels: ['label1', 'label2', 'label3'],
datasets: [{
data: [10, 20, NaN],
backgroundColor: ['red', 'green', 'blue'],
borderWidth: 2,
borderColor: '#000'
}]
},
options: config
});
var meta = chart.getDatasetMeta(0);
spyOn(chart, 'update').and.callThrough();
var legendItem = chart.legend.legendItems[0];
config.legend.onClick.call(chart.legend, null, legendItem);
expect(meta.data[0].hidden).toBe(true);
expect(chart.update).toHaveBeenCalled();
config.legend.onClick.call(chart.legend, null, legendItem);
expect(meta.data[0].hidden).toBe(false);
});
});
});

View File

@ -0,0 +1,403 @@
describe('Deprecations', function() {
describe('Version 2.8.0', function() {
describe('Chart.layoutService', function() {
it('should be defined and an alias of Chart.layouts', function() {
expect(Chart.layoutService).toBeDefined();
expect(Chart.layoutService).toBe(Chart.layouts);
});
});
});
describe('Version 2.7.0', function() {
describe('Chart.Controller.update(duration, lazy)', function() {
it('should add an animation with the provided options', function() {
var chart = acquireChart({
type: 'doughnut',
options: {
animation: {
easing: 'linear',
duration: 500
}
}
});
spyOn(Chart.animationService, 'addAnimation');
chart.update(800, false);
expect(Chart.animationService.addAnimation).toHaveBeenCalledWith(
chart,
jasmine.objectContaining({easing: 'linear'}),
800,
false
);
});
});
describe('Chart.Controller.render(duration, lazy)', function() {
it('should add an animation with the provided options', function() {
var chart = acquireChart({
type: 'doughnut',
options: {
animation: {
easing: 'linear',
duration: 500
}
}
});
spyOn(Chart.animationService, 'addAnimation');
chart.render(800, true);
expect(Chart.animationService.addAnimation).toHaveBeenCalledWith(
chart,
jasmine.objectContaining({easing: 'linear'}),
800,
true
);
});
});
describe('Chart.helpers.indexOf', function() {
it('should be defined and a function', function() {
expect(Chart.helpers.indexOf).toBeDefined();
expect(typeof Chart.helpers.indexOf).toBe('function');
});
it('should returns the correct index', function() {
expect(Chart.helpers.indexOf([1, 2, 42], 42)).toBe(2);
expect(Chart.helpers.indexOf([1, 2, 42], 3)).toBe(-1);
expect(Chart.helpers.indexOf([1, 42, 2, 42], 42, 2)).toBe(3);
expect(Chart.helpers.indexOf([1, 42, 2, 42], 3, 2)).toBe(-1);
});
});
describe('Chart.helpers.clear', function() {
it('should be defined and an alias of Chart.helpers.canvas.clear', function() {
expect(Chart.helpers.clear).toBeDefined();
expect(Chart.helpers.clear).toBe(Chart.helpers.canvas.clear);
});
});
describe('Chart.helpers.getValueOrDefault', function() {
it('should be defined and an alias of Chart.helpers.valueOrDefault', function() {
expect(Chart.helpers.getValueOrDefault).toBeDefined();
expect(Chart.helpers.getValueOrDefault).toBe(Chart.helpers.valueOrDefault);
});
});
describe('Chart.helpers.getValueAtIndexOrDefault', function() {
it('should be defined and an alias of Chart.helpers.valueAtIndexOrDefault', function() {
expect(Chart.helpers.getValueAtIndexOrDefault).toBeDefined();
expect(Chart.helpers.getValueAtIndexOrDefault).toBe(Chart.helpers.valueAtIndexOrDefault);
});
});
describe('Chart.helpers.easingEffects', function() {
it('should be defined and an alias of Chart.helpers.easing.effects', function() {
expect(Chart.helpers.easingEffects).toBeDefined();
expect(Chart.helpers.easingEffects).toBe(Chart.helpers.easing.effects);
});
});
describe('Chart.helpers.drawRoundedRectangle', function() {
it('should be defined and a function', function() {
expect(Chart.helpers.drawRoundedRectangle).toBeDefined();
expect(typeof Chart.helpers.drawRoundedRectangle).toBe('function');
});
it('should call Chart.helpers.canvas.roundedRect', function() {
var ctx = window.createMockContext();
spyOn(Chart.helpers.canvas, 'roundedRect');
Chart.helpers.drawRoundedRectangle(ctx, 10, 20, 30, 40, 5);
var calls = ctx.getCalls();
expect(calls[0]).toEqual({name: 'beginPath', args: []});
expect(calls[calls.length - 1]).toEqual({name: 'closePath', args: []});
expect(Chart.helpers.canvas.roundedRect).toHaveBeenCalledWith(ctx, 10, 20, 30, 40, 5);
});
});
describe('Chart.helpers.addEvent', function() {
it('should be defined and a function', function() {
expect(Chart.helpers.addEvent).toBeDefined();
expect(typeof Chart.helpers.addEvent).toBe('function');
});
it('should correctly add event listener', function() {
var listener = jasmine.createSpy('spy');
Chart.helpers.addEvent(window, 'test', listener);
window.dispatchEvent(new Event('test'));
expect(listener).toHaveBeenCalled();
});
});
describe('Chart.helpers.removeEvent', function() {
it('should be defined and a function', function() {
expect(Chart.helpers.removeEvent).toBeDefined();
expect(typeof Chart.helpers.removeEvent).toBe('function');
});
it('should correctly remove event listener', function() {
var listener = jasmine.createSpy('spy');
Chart.helpers.addEvent(window, 'test', listener);
Chart.helpers.removeEvent(window, 'test', listener);
window.dispatchEvent(new Event('test'));
expect(listener).not.toHaveBeenCalled();
});
});
});
describe('Version 2.6.0', function() {
// https://github.com/chartjs/Chart.js/issues/2481
describe('Chart.Controller', function() {
it('should be defined and an alias of Chart', function() {
expect(Chart.Controller).toBeDefined();
expect(Chart.Controller).toBe(Chart);
});
it('should be prototype of chart instances', function() {
var chart = acquireChart({});
expect(chart.constructor).toBe(Chart.Controller);
expect(chart instanceof Chart.Controller).toBeTruthy();
expect(Chart.Controller.prototype.isPrototypeOf(chart)).toBeTruthy();
});
});
describe('chart.chart', function() {
it('should be defined and an alias of chart', function() {
var chart = acquireChart({});
var proxy = chart.chart;
expect(proxy).toBeDefined();
expect(proxy).toBe(chart);
});
it('should defined previously existing properties', function() {
var chart = acquireChart({}, {
canvas: {
style: 'width: 140px; height: 320px'
}
});
var proxy = chart.chart;
expect(proxy.config instanceof Object).toBeTruthy();
expect(proxy.controller instanceof Chart.Controller).toBeTruthy();
expect(proxy.canvas instanceof HTMLCanvasElement).toBeTruthy();
expect(proxy.ctx instanceof CanvasRenderingContext2D).toBeTruthy();
expect(proxy.currentDevicePixelRatio).toBe(window.devicePixelRatio || 1);
expect(proxy.aspectRatio).toBe(140 / 320);
expect(proxy.height).toBe(320);
expect(proxy.width).toBe(140);
});
});
describe('Chart.Animation.animationObject', function() {
it('should be defined and an alias of Chart.Animation', function(done) {
var animation = null;
acquireChart({
options: {
animation: {
duration: 50,
onComplete: function(arg) {
animation = arg;
}
}
}
});
setTimeout(function() {
expect(animation).not.toBeNull();
expect(animation.animationObject).toBeDefined();
expect(animation.animationObject).toBe(animation);
done();
}, 200);
});
});
describe('Chart.Animation.chartInstance', function() {
it('should be defined and an alias of Chart.Animation.chart', function(done) {
var animation = null;
var chart = acquireChart({
options: {
animation: {
duration: 50,
onComplete: function(arg) {
animation = arg;
}
}
}
});
setTimeout(function() {
expect(animation).not.toBeNull();
expect(animation.chartInstance).toBeDefined();
expect(animation.chartInstance).toBe(chart);
done();
}, 200);
});
});
describe('Chart.elements.Line: fill option', function() {
it('should decode "zero", "top" and "bottom" as "origin", "start" and "end"', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [
{fill: 'zero'},
{fill: 'bottom'},
{fill: 'top'},
]
}
});
['origin', 'start', 'end'].forEach(function(expected, index) {
var meta = chart.getDatasetMeta(index);
expect(meta.$filler).toBeDefined();
expect(meta.$filler.fill).toBe(expected);
});
});
});
describe('Chart.helpers.callCallback', function() {
it('should be defined and an alias of Chart.helpers.callback', function() {
expect(Chart.helpers.callCallback).toBeDefined();
expect(Chart.helpers.callCallback).toBe(Chart.helpers.callback);
});
});
describe('Time Axis: unitStepSize option', function() {
it('should use the stepSize property', function() {
var chart = window.acquireChart({
type: 'line',
data: {
labels: ['2015-01-01T20:00:00', '2015-01-01T21:00:00'],
},
options: {
scales: {
xAxes: [{
id: 'time',
type: 'time',
bounds: 'ticks',
time: {
unit: 'hour',
unitStepSize: 2
}
}]
}
}
});
var ticks = chart.scales.time.getTicks().map(function(tick) {
return tick.label;
});
expect(ticks).toEqual(['8PM', '10PM']);
});
});
});
describe('Version 2.5.0', function() {
describe('Chart.PluginBase', function() {
it('should exist and extendable', function() {
expect(Chart.PluginBase).toBeDefined();
expect(Chart.PluginBase.extend).toBeDefined();
});
});
describe('IPlugin.afterScaleUpdate', function() {
it('should be called after the chart as been layed out', function() {
var sequence = [];
var plugin = {};
var hooks = [
'beforeLayout',
'afterScaleUpdate',
'afterLayout'
];
var override = Chart.layouts.update;
Chart.layouts.update = function() {
sequence.push('layoutUpdate');
override.apply(this, arguments);
};
hooks.forEach(function(name) {
plugin[name] = function() {
sequence.push(name);
};
});
window.acquireChart({plugins: [plugin]});
expect(sequence).toEqual([].concat(
'beforeLayout',
'layoutUpdate',
'afterScaleUpdate',
'afterLayout'
));
});
});
});
describe('Version 2.4.0', function() {
describe('x-axis mode', function() {
it ('behaves like index mode with intersect: false', function() {
var data = {
datasets: [{
label: 'Dataset 1',
data: [10, 20, 30],
pointHoverBorderColor: 'rgb(255, 0, 0)',
pointHoverBackgroundColor: 'rgb(0, 255, 0)'
}, {
label: 'Dataset 2',
data: [40, 40, 40],
pointHoverBorderColor: 'rgb(0, 0, 255)',
pointHoverBackgroundColor: 'rgb(0, 255, 255)'
}],
labels: ['Point 1', 'Point 2', 'Point 3']
};
var chart = window.acquireChart({
type: 'line',
data: data
});
var meta0 = chart.getDatasetMeta(0);
var meta1 = chart.getDatasetMeta(1);
var evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: 0,
y: 0
};
var elements = Chart.Interaction.modes['x-axis'](chart, evt);
expect(elements).toEqual([meta0.data[0], meta1.data[0]]);
});
});
});
describe('Version 2.1.5', function() {
// https://github.com/chartjs/Chart.js/pull/2752
describe('Chart.pluginService', function() {
it('should be defined and an alias of Chart.plugins', function() {
expect(Chart.pluginService).toBeDefined();
expect(Chart.pluginService).toBe(Chart.plugins);
});
});
describe('Chart.Legend', function() {
it('should be defined and an instance of Chart.Element', function() {
var legend = new Chart.Legend({});
expect(Chart.Legend).toBeDefined();
expect(legend).not.toBe(undefined);
expect(legend instanceof Chart.Element).toBeTruthy();
});
});
describe('Chart.Title', function() {
it('should be defined and an instance of Chart.Element', function() {
var title = new Chart.Title({});
expect(Chart.Title).toBeDefined();
expect(title).not.toBe(undefined);
expect(title instanceof Chart.Element).toBeTruthy();
});
});
});
});

View File

@ -0,0 +1,50 @@
'use strict';
describe('Chart.helpers.canvas', function() {
var helpers = Chart.helpers;
describe('clear', function() {
it('should clear the chart canvas', function() {
var chart = acquireChart({}, {
canvas: {
style: 'width: 150px; height: 245px'
}
});
spyOn(chart.ctx, 'clearRect');
helpers.canvas.clear(chart);
expect(chart.ctx.clearRect.calls.count()).toBe(1);
expect(chart.ctx.clearRect.calls.first().object).toBe(chart.ctx);
expect(chart.ctx.clearRect.calls.first().args).toEqual([0, 0, 150, 245]);
});
});
describe('roundedRect', function() {
it('should create a rounded rectangle path', function() {
var context = window.createMockContext();
helpers.canvas.roundedRect(context, 10, 20, 30, 40, 5);
expect(context.getCalls()).toEqual([
{name: 'moveTo', args: [15, 20]},
{name: 'lineTo', args: [35, 20]},
{name: 'quadraticCurveTo', args: [40, 20, 40, 25]},
{name: 'lineTo', args: [40, 55]},
{name: 'quadraticCurveTo', args: [40, 60, 35, 60]},
{name: 'lineTo', args: [15, 60]},
{name: 'quadraticCurveTo', args: [10, 60, 10, 55]},
{name: 'lineTo', args: [10, 25]},
{name: 'quadraticCurveTo', args: [10, 20, 15, 20]}
]);
});
it('should optimize path if radius is 0', function() {
var context = window.createMockContext();
helpers.canvas.roundedRect(context, 10, 20, 30, 40, 0);
expect(context.getCalls()).toEqual([{name: 'rect', args: [10, 20, 30, 40]}]);
});
});
});

View File

@ -0,0 +1,425 @@
'use strict';
describe('Chart.helpers.core', function() {
var helpers = Chart.helpers;
describe('noop', function() {
it('should be callable', function() {
expect(helpers.noop).toBeDefined();
expect(typeof helpers.noop).toBe('function');
expect(typeof helpers.noop.call).toBe('function');
});
it('should returns "undefined"', function() {
expect(helpers.noop(42)).not.toBeDefined();
expect(helpers.noop.call(this, 42)).not.toBeDefined();
});
});
describe('isArray', function() {
it('should return true if value is an array', function() {
expect(helpers.isArray([])).toBeTruthy();
expect(helpers.isArray([42])).toBeTruthy();
expect(helpers.isArray(new Array())).toBeTruthy();
expect(helpers.isArray(Array.prototype)).toBeTruthy();
});
it('should return false if value is not an array', function() {
expect(helpers.isArray()).toBeFalsy();
expect(helpers.isArray({})).toBeFalsy();
expect(helpers.isArray(undefined)).toBeFalsy();
expect(helpers.isArray(null)).toBeFalsy();
expect(helpers.isArray(true)).toBeFalsy();
expect(helpers.isArray(false)).toBeFalsy();
expect(helpers.isArray(42)).toBeFalsy();
expect(helpers.isArray('Array')).toBeFalsy();
expect(helpers.isArray({__proto__: Array.prototype})).toBeFalsy();
});
});
describe('isObject', function() {
it('should return true if value is an object', function() {
expect(helpers.isObject({})).toBeTruthy();
expect(helpers.isObject({a: 42})).toBeTruthy();
expect(helpers.isObject(new Object())).toBeTruthy();
});
it('should return false if value is not an object', function() {
expect(helpers.isObject()).toBeFalsy();
expect(helpers.isObject(undefined)).toBeFalsy();
expect(helpers.isObject(null)).toBeFalsy();
expect(helpers.isObject(true)).toBeFalsy();
expect(helpers.isObject(false)).toBeFalsy();
expect(helpers.isObject(42)).toBeFalsy();
expect(helpers.isObject('Object')).toBeFalsy();
expect(helpers.isObject([])).toBeFalsy();
expect(helpers.isObject([42])).toBeFalsy();
expect(helpers.isObject(new Array())).toBeFalsy();
expect(helpers.isObject(new Date())).toBeFalsy();
});
});
describe('isNullOrUndef', function() {
it('should return true if value is null/undefined', function() {
expect(helpers.isNullOrUndef(null)).toBeTruthy();
expect(helpers.isNullOrUndef(undefined)).toBeTruthy();
});
it('should return false if value is not null/undefined', function() {
expect(helpers.isNullOrUndef(true)).toBeFalsy();
expect(helpers.isNullOrUndef(false)).toBeFalsy();
expect(helpers.isNullOrUndef('')).toBeFalsy();
expect(helpers.isNullOrUndef('String')).toBeFalsy();
expect(helpers.isNullOrUndef(0)).toBeFalsy();
expect(helpers.isNullOrUndef([])).toBeFalsy();
expect(helpers.isNullOrUndef({})).toBeFalsy();
expect(helpers.isNullOrUndef([42])).toBeFalsy();
expect(helpers.isNullOrUndef(new Date())).toBeFalsy();
});
});
describe('valueOrDefault', function() {
it('should return value if defined', function() {
var object = {};
var array = [];
expect(helpers.valueOrDefault(null, 42)).toBe(null);
expect(helpers.valueOrDefault(false, 42)).toBe(false);
expect(helpers.valueOrDefault(object, 42)).toBe(object);
expect(helpers.valueOrDefault(array, 42)).toBe(array);
expect(helpers.valueOrDefault('', 42)).toBe('');
expect(helpers.valueOrDefault(0, 42)).toBe(0);
});
it('should return default if undefined', function() {
expect(helpers.valueOrDefault(undefined, 42)).toBe(42);
expect(helpers.valueOrDefault({}.foo, 42)).toBe(42);
});
});
describe('valueAtIndexOrDefault', function() {
it('should return the passed value if not an array', function() {
expect(helpers.valueAtIndexOrDefault(0, 0, 42)).toBe(0);
expect(helpers.valueAtIndexOrDefault('', 0, 42)).toBe('');
expect(helpers.valueAtIndexOrDefault(null, 0, 42)).toBe(null);
expect(helpers.valueAtIndexOrDefault(false, 0, 42)).toBe(false);
expect(helpers.valueAtIndexOrDefault(98, 0, 42)).toBe(98);
});
it('should return the value at index if defined', function() {
expect(helpers.valueAtIndexOrDefault([1, false, 'foo'], 1, 42)).toBe(false);
expect(helpers.valueAtIndexOrDefault([1, false, 'foo'], 2, 42)).toBe('foo');
});
it('should return the default value if the passed value is undefined', function() {
expect(helpers.valueAtIndexOrDefault(undefined, 0, 42)).toBe(42);
});
it('should return the default value if value at index is undefined', function() {
expect(helpers.valueAtIndexOrDefault([1, false, 'foo'], 3, 42)).toBe(42);
expect(helpers.valueAtIndexOrDefault([1, undefined, 'foo'], 1, 42)).toBe(42);
});
});
describe('callback', function() {
it('should return undefined if fn is not a function', function() {
expect(helpers.callback()).not.toBeDefined();
expect(helpers.callback(null)).not.toBeDefined();
expect(helpers.callback(42)).not.toBeDefined();
expect(helpers.callback([])).not.toBeDefined();
expect(helpers.callback({})).not.toBeDefined();
});
it('should call fn with the given args', function() {
var spy = jasmine.createSpy('spy');
helpers.callback(spy);
helpers.callback(spy, []);
helpers.callback(spy, ['foo']);
helpers.callback(spy, [42, 'bar']);
expect(spy.calls.argsFor(0)).toEqual([]);
expect(spy.calls.argsFor(1)).toEqual([]);
expect(spy.calls.argsFor(2)).toEqual(['foo']);
expect(spy.calls.argsFor(3)).toEqual([42, 'bar']);
});
it('should call fn with the given scope', function() {
var spy = jasmine.createSpy('spy');
var scope = {};
helpers.callback(spy);
helpers.callback(spy, [], null);
helpers.callback(spy, [], undefined);
helpers.callback(spy, [], scope);
expect(spy.calls.all()[0].object).toBe(window);
expect(spy.calls.all()[1].object).toBe(window);
expect(spy.calls.all()[2].object).toBe(window);
expect(spy.calls.all()[3].object).toBe(scope);
});
it('should return the value returned by fn', function() {
expect(helpers.callback(helpers.noop, [41])).toBe(undefined);
expect(helpers.callback(function(i) {
return i + 1;
}, [41])).toBe(42);
});
});
describe('each', function() {
it('should iterate over an array forward if reverse === false', function() {
var scope = {};
var scopes = [];
var items = [];
var keys = [];
helpers.each(['foo', 'bar', 42], function(item, key) {
scopes.push(this);
items.push(item);
keys.push(key);
}, scope);
expect(scopes).toEqual([scope, scope, scope]);
expect(items).toEqual(['foo', 'bar', 42]);
expect(keys).toEqual([0, 1, 2]);
});
it('should iterate over an array backward if reverse === true', function() {
var scope = {};
var scopes = [];
var items = [];
var keys = [];
helpers.each(['foo', 'bar', 42], function(item, key) {
scopes.push(this);
items.push(item);
keys.push(key);
}, scope, true);
expect(scopes).toEqual([scope, scope, scope]);
expect(items).toEqual([42, 'bar', 'foo']);
expect(keys).toEqual([2, 1, 0]);
});
it('should iterate over object properties', function() {
var scope = {};
var scopes = [];
var items = [];
helpers.each({a: 'foo', b: 'bar', c: 42}, function(item, key) {
scopes.push(this);
items[key] = item;
}, scope);
expect(scopes).toEqual([scope, scope, scope]);
expect(items).toEqual(jasmine.objectContaining({a: 'foo', b: 'bar', c: 42}));
});
it('should not throw when called with a non iterable object', function() {
expect(function() {
helpers.each(undefined);
}).not.toThrow();
expect(function() {
helpers.each(null);
}).not.toThrow();
expect(function() {
helpers.each(42);
}).not.toThrow();
});
});
describe('arrayEquals', function() {
it('should return false if arrays are not the same', function() {
expect(helpers.arrayEquals([], [42])).toBeFalsy();
expect(helpers.arrayEquals([42], ['42'])).toBeFalsy();
expect(helpers.arrayEquals([1, 2, 3], [1, 2, 3, 4])).toBeFalsy();
expect(helpers.arrayEquals(['foo', 'bar'], ['bar', 'foo'])).toBeFalsy();
expect(helpers.arrayEquals([1, 2, 3], [1, 2, 'foo'])).toBeFalsy();
expect(helpers.arrayEquals([1, 2, [3, 4]], [1, 2, [3, 'foo']])).toBeFalsy();
expect(helpers.arrayEquals([{a: 42}], [{a: 42}])).toBeFalsy();
});
it('should return false if arrays are not the same', function() {
var o0 = {};
var o1 = {};
var o2 = {};
expect(helpers.arrayEquals([], [])).toBeTruthy();
expect(helpers.arrayEquals([1, 2, 3], [1, 2, 3])).toBeTruthy();
expect(helpers.arrayEquals(['foo', 'bar'], ['foo', 'bar'])).toBeTruthy();
expect(helpers.arrayEquals([true, false, true], [true, false, true])).toBeTruthy();
expect(helpers.arrayEquals([o0, o1, o2], [o0, o1, o2])).toBeTruthy();
});
});
describe('clone', function() {
it('should clone primitive values', function() {
expect(helpers.clone()).toBe(undefined);
expect(helpers.clone(null)).toBe(null);
expect(helpers.clone(true)).toBe(true);
expect(helpers.clone(42)).toBe(42);
expect(helpers.clone('foo')).toBe('foo');
});
it('should perform a deep copy of arrays', function() {
var o0 = {a: 42};
var o1 = {s: 's'};
var a0 = ['bar'];
var a1 = [a0, o0, 2];
var f0 = function() {};
var input = [a1, o1, f0, 42, 'foo'];
var output = helpers.clone(input);
expect(output).toEqual(input);
expect(output).not.toBe(input);
expect(output[0]).not.toBe(a1);
expect(output[0][0]).not.toBe(a0);
expect(output[1]).not.toBe(o1);
});
it('should perform a deep copy of objects', function() {
var a0 = ['bar'];
var a1 = [1, 2, 3];
var o0 = {a: a1, i: 42};
var f0 = function() {};
var input = {o: o0, a: a0, f: f0, s: 'foo', i: 42};
var output = helpers.clone(input);
expect(output).toEqual(input);
expect(output).not.toBe(input);
expect(output.o).not.toBe(o0);
expect(output.o.a).not.toBe(a1);
expect(output.a).not.toBe(a0);
});
});
describe('merge', function() {
it('should update target and return it', function() {
var target = {a: 1};
var result = helpers.merge(target, {a: 2, b: 'foo'});
expect(target).toEqual({a: 2, b: 'foo'});
expect(target).toBe(result);
});
it('should return target if not an object', function() {
expect(helpers.merge(undefined, {a: 42})).toEqual(undefined);
expect(helpers.merge(null, {a: 42})).toEqual(null);
expect(helpers.merge('foo', {a: 42})).toEqual('foo');
expect(helpers.merge(['foo', 'bar'], {a: 42})).toEqual(['foo', 'bar']);
});
it('should ignore sources which are not objects', function() {
expect(helpers.merge({a: 42})).toEqual({a: 42});
expect(helpers.merge({a: 42}, null)).toEqual({a: 42});
expect(helpers.merge({a: 42}, 42)).toEqual({a: 42});
});
it('should recursively overwrite target with source properties', function() {
expect(helpers.merge({a: {b: 1}}, {a: {c: 2}})).toEqual({a: {b: 1, c: 2}});
expect(helpers.merge({a: {b: 1}}, {a: {b: 2}})).toEqual({a: {b: 2}});
expect(helpers.merge({a: [1, 2]}, {a: [3, 4]})).toEqual({a: [3, 4]});
expect(helpers.merge({a: 42}, {a: {b: 0}})).toEqual({a: {b: 0}});
expect(helpers.merge({a: 42}, {a: null})).toEqual({a: null});
expect(helpers.merge({a: 42}, {a: undefined})).toEqual({a: undefined});
});
it('should merge multiple sources in the correct order', function() {
var t0 = {a: {b: 1, c: [1, 2]}};
var s0 = {a: {d: 3}, e: {f: 4}};
var s1 = {a: {b: 5}};
var s2 = {a: {c: [6, 7]}, e: 'foo'};
expect(helpers.merge(t0, [s0, s1, s2])).toEqual({a: {b: 5, c: [6, 7], d: 3}, e: 'foo'});
});
it('should deep copy merged values from sources', function() {
var a0 = ['foo'];
var a1 = [1, 2, 3];
var o0 = {a: a1, i: 42};
var output = helpers.merge({}, {a: a0, o: o0});
expect(output).toEqual({a: a0, o: o0});
expect(output.a).not.toBe(a0);
expect(output.o).not.toBe(o0);
expect(output.o.a).not.toBe(a1);
});
});
describe('mergeIf', function() {
it('should update target and return it', function() {
var target = {a: 1};
var result = helpers.mergeIf(target, {a: 2, b: 'foo'});
expect(target).toEqual({a: 1, b: 'foo'});
expect(target).toBe(result);
});
it('should return target if not an object', function() {
expect(helpers.mergeIf(undefined, {a: 42})).toEqual(undefined);
expect(helpers.mergeIf(null, {a: 42})).toEqual(null);
expect(helpers.mergeIf('foo', {a: 42})).toEqual('foo');
expect(helpers.mergeIf(['foo', 'bar'], {a: 42})).toEqual(['foo', 'bar']);
});
it('should ignore sources which are not objects', function() {
expect(helpers.mergeIf({a: 42})).toEqual({a: 42});
expect(helpers.mergeIf({a: 42}, null)).toEqual({a: 42});
expect(helpers.mergeIf({a: 42}, 42)).toEqual({a: 42});
});
it('should recursively copy source properties in target only if they do not exist in target', function() {
expect(helpers.mergeIf({a: {b: 1}}, {a: {c: 2}})).toEqual({a: {b: 1, c: 2}});
expect(helpers.mergeIf({a: {b: 1}}, {a: {b: 2}})).toEqual({a: {b: 1}});
expect(helpers.mergeIf({a: [1, 2]}, {a: [3, 4]})).toEqual({a: [1, 2]});
expect(helpers.mergeIf({a: 0}, {a: {b: 2}})).toEqual({a: 0});
expect(helpers.mergeIf({a: null}, {a: 42})).toEqual({a: null});
expect(helpers.mergeIf({a: undefined}, {a: 42})).toEqual({a: undefined});
});
it('should merge multiple sources in the correct order', function() {
var t0 = {a: {b: 1, c: [1, 2]}};
var s0 = {a: {d: 3}, e: {f: 4}};
var s1 = {a: {b: 5}};
var s2 = {a: {c: [6, 7]}, e: 'foo'};
expect(helpers.mergeIf(t0, [s0, s1, s2])).toEqual({a: {b: 1, c: [1, 2], d: 3}, e: {f: 4}});
});
it('should deep copy merged values from sources', function() {
var a0 = ['foo'];
var a1 = [1, 2, 3];
var o0 = {a: a1, i: 42};
var output = helpers.mergeIf({}, {a: a0, o: o0});
expect(output).toEqual({a: a0, o: o0});
expect(output.a).not.toBe(a0);
expect(output.o).not.toBe(o0);
expect(output.o.a).not.toBe(a1);
});
});
describe('extend', function() {
it('should merge object properties in target and return target', function() {
var target = {a: 'abc', b: 56};
var object = {b: 0, c: [2, 5, 6]};
var result = helpers.extend(target, object);
expect(result).toBe(target);
expect(target).toEqual({a: 'abc', b: 0, c: [2, 5, 6]});
});
it('should merge multiple objects properties in target', function() {
var target = {a: 0, b: 1};
var o0 = {a: 2, c: 3, d: 4};
var o1 = {a: 5, c: 6};
var o2 = {a: 7, e: 8};
helpers.extend(target, o0, o1, o2);
expect(target).toEqual({a: 7, b: 1, c: 6, d: 4, e: 8});
});
it('should not deeply merge object properties in target', function() {
var target = {a: {b: 0, c: 1}};
var object = {a: {b: 2, d: 3}};
helpers.extend(target, object);
expect(target).toEqual({a: {b: 2, d: 3}});
expect(target.a).toBe(object.a);
});
});
describe('inherits', function() {
it('should return a derived class', function() {
var A = function() {};
A.prototype.p0 = 41;
A.prototype.p1 = function() {
return '42';
};
A.inherits = helpers.inherits;
var B = A.inherits({p0: 43, p2: [44]});
var C = A.inherits({p3: 45, p4: [46]});
var b = new B();
expect(b instanceof A).toBeTruthy();
expect(b instanceof B).toBeTruthy();
expect(b instanceof C).toBeFalsy();
expect(b.p0).toBe(43);
expect(b.p1()).toBe('42');
expect(b.p2).toEqual([44]);
});
});
});

View File

@ -0,0 +1,61 @@
'use strict';
describe('Chart.helpers.easing', function() {
var easing = Chart.helpers.easing;
describe('effects', function() {
var expected = {
easeInOutBack: [-0, -0.03751855, -0.09255566, -0.07883348, 0.08992579, 0.5, 0.91007421, 1.07883348, 1.09255566, 1.03751855, 1],
easeInOutBounce: [0, 0.03, 0.11375, 0.045, 0.34875, 0.5, 0.65125, 0.955, 0.88625, 0.97, 1],
easeInOutCirc: [-0, 0.01010205, 0.04174243, 0.1, 0.2, 0.5, 0.8, 0.9, 0.95825757, 0.98989795, 1],
easeInOutCubic: [0, 0.004, 0.032, 0.108, 0.256, 0.5, 0.744, 0.892, 0.968, 0.996, 1],
easeInOutElastic: [0, 0.00033916, -0.00390625, 0.02393889, -0.11746158, 0.5, 1.11746158, 0.97606111, 1.00390625, 0.99966084, 1],
easeInOutExpo: [0, 0.00195313, 0.0078125, 0.03125, 0.125, 0.5, 0.875, 0.96875, 0.9921875, 0.99804688, 1],
easeInOutQuad: [0, 0.02, 0.08, 0.18, 0.32, 0.5, 0.68, 0.82, 0.92, 0.98, 1],
easeInOutQuart: [0, 0.0008, 0.0128, 0.0648, 0.2048, 0.5, 0.7952, 0.9352, 0.9872, 0.9992, 1],
easeInOutQuint: [0, 0.00016, 0.00512, 0.03888, 0.16384, 0.5, 0.83616, 0.96112, 0.99488, 0.99984, 1],
easeInOutSine: [-0, 0.02447174, 0.0954915, 0.20610737, 0.3454915, 0.5, 0.6545085, 0.79389263, 0.9045085, 0.97552826, 1],
easeInBack: [-0, -0.01431422, -0.04645056, -0.08019954, -0.09935168, -0.0876975, -0.02902752, 0.09286774, 0.29419776, 0.59117202, 1],
easeInBounce: [0, 0.011875, 0.06, 0.069375, 0.2275, 0.234375, 0.09, 0.319375, 0.6975, 0.924375, 1],
easeInCirc: [-0, 0.00501256, 0.0202041, 0.0460608, 0.08348486, 0.1339746, 0.2, 0.28585716, 0.4, 0.56411011, 1],
easeInCubic: [0, 0.001, 0.008, 0.027, 0.064, 0.125, 0.216, 0.343, 0.512, 0.729, 1],
easeInExpo: [0, 0.00195313, 0.00390625, 0.0078125, 0.015625, 0.03125, 0.0625, 0.125, 0.25, 0.5, 1],
easeInElastic: [0, 0.00195313, -0.00195313, -0.00390625, 0.015625, -0.015625, -0.03125, 0.125, -0.125, -0.25, 1],
easeInQuad: [0, 0.01, 0.04, 0.09, 0.16, 0.25, 0.36, 0.49, 0.64, 0.81, 1],
easeInQuart: [0, 0.0001, 0.0016, 0.0081, 0.0256, 0.0625, 0.1296, 0.2401, 0.4096, 0.6561, 1],
easeInQuint: [0, 0.00001, 0.00032, 0.00243, 0.01024, 0.03125, 0.07776, 0.16807, 0.32768, 0.59049, 1],
easeInSine: [0, 0.01231166, 0.04894348, 0.10899348, 0.19098301, 0.29289322, 0.41221475, 0.5460095, 0.69098301, 0.84356553, 1],
easeOutBack: [0, 0.40882798, 0.70580224, 0.90713226, 1.02902752, 1.0876975, 1.09935168, 1.08019954, 1.04645056, 1.01431422, 1],
easeOutBounce: [0, 0.075625, 0.3025, 0.680625, 0.91, 0.765625, 0.7725, 0.930625, 0.94, 0.988125, 1],
easeOutCirc: [0, 0.43588989, 0.6, 0.71414284, 0.8, 0.8660254, 0.91651514, 0.9539392, 0.9797959, 0.99498744, 1],
easeOutElastic: [0, 1.25, 1.125, 0.875, 1.03125, 1.015625, 0.984375, 1.00390625, 1.00195313, 0.99804688, 1],
easeOutExpo: [0, 0.5, 0.75, 0.875, 0.9375, 0.96875, 0.984375, 0.9921875, 0.99609375, 0.99804688, 1],
easeOutCubic: [0, 0.271, 0.488, 0.657, 0.784, 0.875, 0.936, 0.973, 0.992, 0.999, 1],
easeOutQuad: [0, 0.19, 0.36, 0.51, 0.64, 0.75, 0.84, 0.91, 0.96, 0.99, 1],
easeOutQuart: [-0, 0.3439, 0.5904, 0.7599, 0.8704, 0.9375, 0.9744, 0.9919, 0.9984, 0.9999, 1],
easeOutQuint: [0, 0.40951, 0.67232, 0.83193, 0.92224, 0.96875, 0.98976, 0.99757, 0.99968, 0.99999, 1],
easeOutSine: [0, 0.15643447, 0.30901699, 0.4539905, 0.58778525, 0.70710678, 0.80901699, 0.89100652, 0.95105652, 0.98768834, 1],
linear: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]
};
function generate(method) {
var fn = easing.effects[method];
var accuracy = Math.pow(10, 8);
var count = 10;
var values = [];
var i;
for (i = 0; i <= count; ++i) {
values.push(Math.round(accuracy * fn(i / count)) / accuracy);
}
return values;
}
Object.keys(easing.effects).forEach(function(method) {
it ('"' + method + '" should return expected values', function() {
expect(generate(method)).toEqual(expected[method]);
});
});
});
});

View File

@ -0,0 +1,125 @@
'use strict';
describe('Chart.helpers.options', function() {
var options = Chart.helpers.options;
describe('toLineHeight', function() {
it ('should support keyword values', function() {
expect(options.toLineHeight('normal', 16)).toBe(16 * 1.2);
});
it ('should support unitless values', function() {
expect(options.toLineHeight(1.4, 16)).toBe(16 * 1.4);
expect(options.toLineHeight('1.4', 16)).toBe(16 * 1.4);
});
it ('should support length values', function() {
expect(options.toLineHeight('42px', 16)).toBe(42);
expect(options.toLineHeight('1.4em', 16)).toBe(16 * 1.4);
});
it ('should support percentage values', function() {
expect(options.toLineHeight('140%', 16)).toBe(16 * 1.4);
});
it ('should fallback to default (1.2) for invalid values', function() {
expect(options.toLineHeight(null, 16)).toBe(16 * 1.2);
expect(options.toLineHeight(undefined, 16)).toBe(16 * 1.2);
expect(options.toLineHeight('foobar', 16)).toBe(16 * 1.2);
});
});
describe('toPadding', function() {
it ('should support number values', function() {
expect(options.toPadding(4)).toEqual(
{top: 4, right: 4, bottom: 4, left: 4, height: 8, width: 8});
expect(options.toPadding(4.5)).toEqual(
{top: 4.5, right: 4.5, bottom: 4.5, left: 4.5, height: 9, width: 9});
});
it ('should support string values', function() {
expect(options.toPadding('4')).toEqual(
{top: 4, right: 4, bottom: 4, left: 4, height: 8, width: 8});
expect(options.toPadding('4.5')).toEqual(
{top: 4.5, right: 4.5, bottom: 4.5, left: 4.5, height: 9, width: 9});
});
it ('should support object values', function() {
expect(options.toPadding({top: 1, right: 2, bottom: 3, left: 4})).toEqual(
{top: 1, right: 2, bottom: 3, left: 4, height: 4, width: 6});
expect(options.toPadding({top: 1.5, right: 2.5, bottom: 3.5, left: 4.5})).toEqual(
{top: 1.5, right: 2.5, bottom: 3.5, left: 4.5, height: 5, width: 7});
expect(options.toPadding({top: '1', right: '2', bottom: '3', left: '4'})).toEqual(
{top: 1, right: 2, bottom: 3, left: 4, height: 4, width: 6});
});
it ('should fallback to 0 for invalid values', function() {
expect(options.toPadding({top: 'foo', right: 'foo', bottom: 'foo', left: 'foo'})).toEqual(
{top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0});
expect(options.toPadding({top: null, right: null, bottom: null, left: null})).toEqual(
{top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0});
expect(options.toPadding({})).toEqual(
{top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0});
expect(options.toPadding('foo')).toEqual(
{top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0});
expect(options.toPadding(null)).toEqual(
{top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0});
expect(options.toPadding(undefined)).toEqual(
{top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0});
});
});
describe('resolve', function() {
it ('should fallback to the first defined input', function() {
expect(options.resolve([42])).toBe(42);
expect(options.resolve([42, 'foo'])).toBe(42);
expect(options.resolve([undefined, 42, 'foo'])).toBe(42);
expect(options.resolve([42, 'foo', undefined])).toBe(42);
expect(options.resolve([undefined])).toBe(undefined);
});
it ('should correctly handle empty values (null, 0, "")', function() {
expect(options.resolve([0, 'foo'])).toBe(0);
expect(options.resolve(['', 'foo'])).toBe('');
expect(options.resolve([null, 'foo'])).toBe(null);
});
it ('should support indexable options if index is provided', function() {
var input = [42, 'foo', 'bar'];
expect(options.resolve([input], undefined, 0)).toBe(42);
expect(options.resolve([input], undefined, 1)).toBe('foo');
expect(options.resolve([input], undefined, 2)).toBe('bar');
});
it ('should fallback if an indexable option value is undefined', function() {
var input = [42, undefined, 'bar'];
expect(options.resolve([input], undefined, 5)).toBe(undefined);
expect(options.resolve([input, 'foo'], undefined, 1)).toBe('foo');
expect(options.resolve([input, 'foo'], undefined, 5)).toBe('foo');
});
it ('should not handle indexable options if index is undefined', function() {
var array = [42, 'foo', 'bar'];
expect(options.resolve([array])).toBe(array);
expect(options.resolve([array], undefined, undefined)).toBe(array);
});
it ('should support scriptable options if context is provided', function() {
var input = function(context) {
return context.v * 2;
};
expect(options.resolve([42], {v: 42})).toBe(42);
expect(options.resolve([input], {v: 42})).toBe(84);
});
it ('should fallback if a scriptable option returns undefined', function() {
var input = function() {};
expect(options.resolve([input], {v: 42})).toBe(undefined);
expect(options.resolve([input, 'foo'], {v: 42})).toBe('foo');
expect(options.resolve([input, undefined, 'foo'], {v: 42})).toBe('foo');
});
it ('should not handle scriptable options if context is undefined', function() {
var input = function(context) {
return context.v * 2;
};
expect(options.resolve([input])).toBe(input);
expect(options.resolve([input], undefined)).toBe(input);
});
it ('should handle scriptable and indexable option', function() {
var input = function(context) {
return [context.v, undefined, 'bar'];
};
expect(options.resolve([input, 'foo'], {v: 42}, 0)).toBe(42);
expect(options.resolve([input, 'foo'], {v: 42}, 1)).toBe('foo');
expect(options.resolve([input, 'foo'], {v: 42}, 5)).toBe('foo');
expect(options.resolve([input, ['foo', 'bar']], {v: 42}, 1)).toBe('bar');
});
});
});

View File

@ -0,0 +1,401 @@
describe('Platform.dom', function() {
describe('context acquisition', function() {
var canvasId = 'chartjs-canvas';
beforeEach(function() {
var canvas = document.createElement('canvas');
canvas.setAttribute('id', canvasId);
window.document.body.appendChild(canvas);
});
afterEach(function() {
document.getElementById(canvasId).remove();
});
// see https://github.com/chartjs/Chart.js/issues/2807
it('should gracefully handle invalid item', function() {
var chart = new Chart('foobar');
expect(chart).not.toBeValidChart();
chart.destroy();
});
it('should accept a DOM element id', function() {
var canvas = document.getElementById(canvasId);
var chart = new Chart(canvasId);
expect(chart).toBeValidChart();
expect(chart.canvas).toBe(canvas);
expect(chart.ctx).toBe(canvas.getContext('2d'));
chart.destroy();
});
it('should accept a canvas element', function() {
var canvas = document.getElementById(canvasId);
var chart = new Chart(canvas);
expect(chart).toBeValidChart();
expect(chart.canvas).toBe(canvas);
expect(chart.ctx).toBe(canvas.getContext('2d'));
chart.destroy();
});
it('should accept a canvas context2D', function() {
var canvas = document.getElementById(canvasId);
var context = canvas.getContext('2d');
var chart = new Chart(context);
expect(chart).toBeValidChart();
expect(chart.canvas).toBe(canvas);
expect(chart.ctx).toBe(context);
chart.destroy();
});
it('should accept an array containing canvas', function() {
var canvas = document.getElementById(canvasId);
var chart = new Chart([canvas]);
expect(chart).toBeValidChart();
expect(chart.canvas).toBe(canvas);
expect(chart.ctx).toBe(canvas.getContext('2d'));
chart.destroy();
});
it('should accept a canvas from an iframe', function(done) {
var iframe = document.createElement('iframe');
iframe.onload = function() {
var doc = iframe.contentDocument;
doc.body.innerHTML += '<canvas id="chart"></canvas>';
var canvas = doc.getElementById('chart');
var chart = new Chart(canvas);
expect(chart).toBeValidChart();
expect(chart.canvas).toBe(canvas);
expect(chart.ctx).toBe(canvas.getContext('2d'));
chart.destroy();
canvas.remove();
iframe.remove();
done();
};
document.body.appendChild(iframe);
});
});
describe('config.options.aspectRatio', function() {
it('should use default "global" aspect ratio for render and display sizes', function() {
var chart = acquireChart({
options: {
responsive: false
}
}, {
canvas: {
style: 'width: 620px'
}
});
expect(chart).toBeChartOfSize({
dw: 620, dh: 310,
rw: 620, rh: 310,
});
});
it('should use default "chart" aspect ratio for render and display sizes', function() {
var ratio = Chart.defaults.doughnut.aspectRatio;
Chart.defaults.doughnut.aspectRatio = 1;
var chart = acquireChart({
type: 'doughnut',
options: {
responsive: false
}
}, {
canvas: {
style: 'width: 425px'
}
});
Chart.defaults.doughnut.aspectRatio = ratio;
expect(chart).toBeChartOfSize({
dw: 425, dh: 425,
rw: 425, rh: 425,
});
});
it('should use "user" aspect ratio for render and display sizes', function() {
var chart = acquireChart({
options: {
responsive: false,
aspectRatio: 3
}
}, {
canvas: {
style: 'width: 405px'
}
});
expect(chart).toBeChartOfSize({
dw: 405, dh: 135,
rw: 405, rh: 135,
});
});
it('should not apply aspect ratio when height specified', function() {
var chart = acquireChart({
options: {
responsive: false,
aspectRatio: 3
}
}, {
canvas: {
style: 'width: 400px; height: 410px'
}
});
expect(chart).toBeChartOfSize({
dw: 400, dh: 410,
rw: 400, rh: 410,
});
});
});
describe('config.options.responsive: false', function() {
it('should use default canvas size for render and display sizes', function() {
var chart = acquireChart({
options: {
responsive: false
}
}, {
canvas: {
style: ''
}
});
expect(chart).toBeChartOfSize({
dw: 300, dh: 150,
rw: 300, rh: 150,
});
});
it('should use canvas attributes for render and display sizes', function() {
var chart = acquireChart({
options: {
responsive: false
}
}, {
canvas: {
style: '',
width: 305,
height: 245,
}
});
expect(chart).toBeChartOfSize({
dw: 305, dh: 245,
rw: 305, rh: 245,
});
});
it('should use canvas style for render and display sizes (if no attributes)', function() {
var chart = acquireChart({
options: {
responsive: false
}
}, {
canvas: {
style: 'width: 345px; height: 125px'
}
});
expect(chart).toBeChartOfSize({
dw: 345, dh: 125,
rw: 345, rh: 125,
});
});
it('should use attributes for the render size and style for the display size', function() {
var chart = acquireChart({
options: {
responsive: false
}
}, {
canvas: {
style: 'width: 345px; height: 125px;',
width: 165,
height: 85,
}
});
expect(chart).toBeChartOfSize({
dw: 345, dh: 125,
rw: 165, rh: 85,
});
});
// https://github.com/chartjs/Chart.js/issues/3860
it('should support decimal display width and/or height', function() {
var chart = acquireChart({
options: {
responsive: false
}
}, {
canvas: {
style: 'width: 345.42px; height: 125.42px;'
}
});
expect(chart).toBeChartOfSize({
dw: 345, dh: 125,
rw: 345, rh: 125,
});
});
});
describe('config.options.responsive: true (maintainAspectRatio: true)', function() {
it('should fill parent width and use aspect ratio to calculate height', function() {
var chart = acquireChart({
options: {
responsive: true,
maintainAspectRatio: true
}
}, {
canvas: {
style: 'width: 150px; height: 245px'
},
wrapper: {
style: 'width: 300px; height: 350px'
}
});
expect(chart).toBeChartOfSize({
dw: 300, dh: 490,
rw: 300, rh: 490,
});
});
});
describe('controller.destroy', function() {
it('should reset context to default values', function() {
var chart = acquireChart({});
var context = chart.ctx;
chart.destroy();
// https://www.w3.org/TR/2dcontext/#conformance-requirements
Chart.helpers.each({
fillStyle: '#000000',
font: '10px sans-serif',
lineJoin: 'miter',
lineCap: 'butt',
lineWidth: 1,
miterLimit: 10,
shadowBlur: 0,
shadowColor: 'rgba(0, 0, 0, 0)',
shadowOffsetX: 0,
shadowOffsetY: 0,
strokeStyle: '#000000',
textAlign: 'start',
textBaseline: 'alphabetic'
}, function(value, key) {
expect(context[key]).toBe(value);
});
});
it('should restore canvas initial values', function(done) {
var chart = acquireChart({
options: {
responsive: true,
maintainAspectRatio: false
}
}, {
canvas: {
width: 180,
style: 'width: 512px; height: 480px'
},
wrapper: {
style: 'width: 450px; height: 450px; position: relative'
}
});
var canvas = chart.canvas;
var wrapper = canvas.parentNode;
wrapper.style.width = '475px';
waitForResize(chart, function() {
expect(chart).toBeChartOfSize({
dw: 475, dh: 450,
rw: 475, rh: 450,
});
chart.destroy();
expect(canvas.getAttribute('width')).toBe('180');
expect(canvas.getAttribute('height')).toBe(null);
expect(canvas.style.width).toBe('512px');
expect(canvas.style.height).toBe('480px');
expect(canvas.style.display).toBe('');
done();
});
});
});
describe('event handling', function() {
it('should notify plugins about events', function() {
var notifiedEvent;
var plugin = {
afterEvent: function(chart, e) {
notifiedEvent = e;
}
};
var chart = acquireChart({
type: 'line',
data: {
labels: ['A', 'B', 'C', 'D'],
datasets: [{
data: [10, 20, 30, 100]
}]
},
options: {
responsive: true
},
plugins: [plugin]
});
var node = chart.canvas;
var rect = node.getBoundingClientRect();
var clientX = (rect.left + rect.right) / 2;
var clientY = (rect.top + rect.bottom) / 2;
var evt = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true,
clientX: clientX,
clientY: clientY
});
// Manually trigger rather than having an async test
node.dispatchEvent(evt);
// Check that notifiedEvent is correct
expect(notifiedEvent).not.toBe(undefined);
expect(notifiedEvent.native).toBe(evt);
// Is type correctly translated
expect(notifiedEvent.type).toBe(evt.type);
// Relative Position
expect(notifiedEvent.x).toBeCloseToPixel(chart.width / 2);
expect(notifiedEvent.y).toBeCloseToPixel(chart.height / 2);
});
});
});

View File

@ -0,0 +1,265 @@
describe('Plugin.filler', function() {
function decodedFillValues(chart) {
return chart.data.datasets.map(function(dataset, index) {
var meta = chart.getDatasetMeta(index) || {};
expect(meta.$filler).toBeDefined();
return meta.$filler.fill;
});
}
describe('auto', jasmine.specsFromFixtures('plugin.filler'));
describe('dataset.fill', function() {
it('should support boundaries', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [
{fill: 'origin'},
{fill: 'start'},
{fill: 'end'},
]
}
});
expect(decodedFillValues(chart)).toEqual(['origin', 'start', 'end']);
});
it('should support absolute dataset index', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [
{fill: 1},
{fill: 3},
{fill: 0},
{fill: 2},
]
}
});
expect(decodedFillValues(chart)).toEqual([1, 3, 0, 2]);
});
it('should support relative dataset index', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [
{fill: '+3'},
{fill: '-1'},
{fill: '+1'},
{fill: '-2'},
]
}
});
expect(decodedFillValues(chart)).toEqual([
3, // 0 + 3
0, // 1 - 1
3, // 2 + 1
1, // 3 - 2
]);
});
it('should handle default fill when true (origin)', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [
{fill: true},
{fill: false},
]
}
});
expect(decodedFillValues(chart)).toEqual(['origin', false]);
});
it('should ignore self dataset index', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [
{fill: 0},
{fill: '-0'},
{fill: '+0'},
{fill: 3},
]
}
});
expect(decodedFillValues(chart)).toEqual([
false, // 0 === 0
false, // 1 === 1 - 0
false, // 2 === 2 + 0
false, // 3 === 3
]);
});
it('should ignore out of bounds dataset index', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [
{fill: -2},
{fill: 4},
{fill: '-3'},
{fill: '+1'},
]
}
});
expect(decodedFillValues(chart)).toEqual([
false, // 0 - 2 < 0
false, // 1 + 4 > 3
false, // 2 - 3 < 0
false, // 3 + 1 > 3
]);
});
it('should ignore invalid values', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [
{fill: 'foo'},
{fill: '+foo'},
{fill: '-foo'},
{fill: '+1.1'},
{fill: '-2.2'},
{fill: 3.3},
{fill: -4.4},
{fill: NaN},
{fill: Infinity},
{fill: ''},
{fill: null},
{fill: []},
{fill: {}},
{fill: function() {}}
]
}
});
expect(decodedFillValues(chart)).toEqual([
false, // NaN (string)
false, // NaN (string)
false, // NaN (string)
false, // float (string)
false, // float (string)
false, // float (number)
false, // float (number)
false, // NaN
false, // !isFinite
false, // empty string
false, // null
false, // array
false, // object
false, // function
]);
});
});
describe('options.plugins.filler.propagate', function() {
it('should compute propagated fill targets if true', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [
{fill: 'start', hidden: true},
{fill: '-1', hidden: true},
{fill: 1, hidden: true},
{fill: '-2', hidden: true},
{fill: '+1'},
{fill: '+2'},
{fill: '-1'},
{fill: 'end', hidden: true},
]
},
options: {
plugins: {
filler: {
propagate: true
}
}
}
});
expect(decodedFillValues(chart)).toEqual([
'start', // 'start'
'start', // 1 - 1 -> 0 (hidden) -> 'start'
'start', // 1 (hidden) -> 0 (hidden) -> 'start'
'start', // 3 - 2 -> 1 (hidden) -> 0 (hidden) -> 'start'
5, // 4 + 1
'end', // 5 + 2 -> 7 (hidden) -> 'end'
5, // 6 - 1 -> 5
'end', // 'end'
]);
});
it('should preserve initial fill targets if false', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [
{fill: 'start', hidden: true},
{fill: '-1', hidden: true},
{fill: 1, hidden: true},
{fill: '-2', hidden: true},
{fill: '+1'},
{fill: '+2'},
{fill: '-1'},
{fill: 'end', hidden: true},
]
},
options: {
plugins: {
filler: {
propagate: false
}
}
}
});
expect(decodedFillValues(chart)).toEqual([
'start', // 'origin'
0, // 1 - 1
1, // 1
1, // 3 - 2
5, // 4 + 1
7, // 5 + 2
5, // 6 - 1
'end', // 'end'
]);
});
it('should prevent recursive propagation', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [
{fill: '+2', hidden: true},
{fill: '-1', hidden: true},
{fill: '-1', hidden: true},
{fill: '-2'}
]
},
options: {
plugins: {
filler: {
propagate: true
}
}
}
});
expect(decodedFillValues(chart)).toEqual([
false, // 0 + 2 -> 2 (hidden) -> 1 (hidden) -> 0 (loop)
false, // 1 - 1 -> 0 (hidden) -> 2 (hidden) -> 1 (loop)
false, // 2 - 1 -> 1 (hidden) -> 0 (hidden) -> 2 (loop)
false, // 3 - 2 -> 1 (hidden) -> 0 (hidden) -> 2 (hidden) -> 1 (loop)
]);
});
});
});

View File

@ -0,0 +1,480 @@
// Test the rectangle element
describe('Legend block tests', function() {
it('should have the correct default config', function() {
expect(Chart.defaults.global.legend).toEqual({
display: true,
position: 'top',
fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)
reverse: false,
weight: 1000,
// a callback that will handle
onClick: jasmine.any(Function),
onHover: null,
labels: {
boxWidth: 40,
padding: 10,
generateLabels: jasmine.any(Function)
}
});
});
it('should update correctly', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
label: 'dataset1',
backgroundColor: '#f31',
borderCapStyle: 'butt',
borderDash: [2, 2],
borderDashOffset: 5.5,
data: []
}, {
label: 'dataset2',
hidden: true,
borderJoinStyle: 'miter',
data: []
}, {
label: 'dataset3',
borderWidth: 10,
borderColor: 'green',
pointStyle: 'crossRot',
data: []
}],
labels: []
}
});
expect(chart.legend.legendItems).toEqual([{
text: 'dataset1',
fillStyle: '#f31',
hidden: false,
lineCap: 'butt',
lineDash: [2, 2],
lineDashOffset: 5.5,
lineJoin: undefined,
lineWidth: undefined,
strokeStyle: undefined,
pointStyle: undefined,
datasetIndex: 0
}, {
text: 'dataset2',
fillStyle: undefined,
hidden: true,
lineCap: undefined,
lineDash: undefined,
lineDashOffset: undefined,
lineJoin: 'miter',
lineWidth: undefined,
strokeStyle: undefined,
pointStyle: undefined,
datasetIndex: 1
}, {
text: 'dataset3',
fillStyle: undefined,
hidden: false,
lineCap: undefined,
lineDash: undefined,
lineDashOffset: undefined,
lineJoin: undefined,
lineWidth: 10,
strokeStyle: 'green',
pointStyle: 'crossRot',
datasetIndex: 2
}]);
});
it('should filter items', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
label: 'dataset1',
backgroundColor: '#f31',
borderCapStyle: 'butt',
borderDash: [2, 2],
borderDashOffset: 5.5,
data: []
}, {
label: 'dataset2',
hidden: true,
borderJoinStyle: 'miter',
data: [],
legendHidden: true
}, {
label: 'dataset3',
borderWidth: 10,
borderColor: 'green',
pointStyle: 'crossRot',
data: []
}],
labels: []
},
options: {
legend: {
labels: {
filter: function(legendItem, data) {
var dataset = data.datasets[legendItem.datasetIndex];
return !dataset.legendHidden;
}
}
}
}
});
expect(chart.legend.legendItems).toEqual([{
text: 'dataset1',
fillStyle: '#f31',
hidden: false,
lineCap: 'butt',
lineDash: [2, 2],
lineDashOffset: 5.5,
lineJoin: undefined,
lineWidth: undefined,
strokeStyle: undefined,
pointStyle: undefined,
datasetIndex: 0
}, {
text: 'dataset3',
fillStyle: undefined,
hidden: false,
lineCap: undefined,
lineDash: undefined,
lineDashOffset: undefined,
lineJoin: undefined,
lineWidth: 10,
strokeStyle: 'green',
pointStyle: 'crossRot',
datasetIndex: 2
}]);
});
it('should not throw when the label options are missing', function() {
var makeChart = function() {
window.acquireChart({
type: 'bar',
data: {
datasets: [{
label: 'dataset1',
backgroundColor: '#f31',
borderCapStyle: 'butt',
borderDash: [2, 2],
borderDashOffset: 5.5,
data: []
}],
labels: []
},
options: {
legend: {
labels: false,
}
}
});
};
expect(makeChart).not.toThrow();
});
it('should draw correctly', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
label: 'dataset1',
backgroundColor: '#f31',
borderCapStyle: 'butt',
borderDash: [2, 2],
borderDashOffset: 5.5,
data: []
}, {
label: 'dataset2',
hidden: true,
borderJoinStyle: 'miter',
data: []
}, {
label: 'dataset3',
borderWidth: 10,
borderColor: 'green',
data: []
}],
labels: []
}
});
expect(chart.legend.legendHitBoxes.length).toBe(3);
[
{h: 12, l: 101, t: 10, w: 93},
{h: 12, l: 205, t: 10, w: 93},
{h: 12, l: 308, t: 10, w: 93}
].forEach(function(expected, i) {
expect(chart.legend.legendHitBoxes[i].height).toBeCloseToPixel(expected.h);
expect(chart.legend.legendHitBoxes[i].left).toBeCloseToPixel(expected.l);
expect(chart.legend.legendHitBoxes[i].top).toBeCloseToPixel(expected.t);
expect(chart.legend.legendHitBoxes[i].width).toBeCloseToPixel(expected.w);
});
// NOTE(SB) We should get ride of the following tests and use image diff instead.
// For now, as discussed with Evert Timberg, simply comment out.
// See http://humblesoftware.github.io/js-imagediff/test.html
/* chart.legend.ctx = window.createMockContext();
chart.update();
expect(chart.legend.ctx .getCalls()).toEqual([{
"name": "measureText",
"args": ["dataset1"]
}, {
"name": "measureText",
"args": ["dataset2"]
}, {
"name": "measureText",
"args": ["dataset3"]
}, {
"name": "measureText",
"args": ["dataset1"]
}, {
"name": "measureText",
"args": ["dataset2"]
}, {
"name": "measureText",
"args": ["dataset3"]
}, {
"name": "setLineWidth",
"args": [0.5]
}, {
"name": "setStrokeStyle",
"args": ["#666"]
}, {
"name": "setFillStyle",
"args": ["#666"]
}, {
"name": "measureText",
"args": ["dataset1"]
}, {
"name": "save",
"args": []
}, {
"name": "setFillStyle",
"args": ["#f31"]
}, {
"name": "setLineCap",
"args": ["butt"]
}, {
"name": "setLineDashOffset",
"args": [5.5]
}, {
"name": "setLineJoin",
"args": ["miter"]
}, {
"name": "setLineWidth",
"args": [3]
}, {
"name": "setStrokeStyle",
"args": ["rgba(0,0,0,0.1)"]
}, {
"name": "setLineDash",
"args": [
[2, 2]
]
}, {
"name": "strokeRect",
"args": [114, 110, 40, 12]
}, {
"name": "fillRect",
"args": [114, 110, 40, 12]
}, {
"name": "restore",
"args": []
}, {
"name": "fillText",
"args": ["dataset1", 160, 110]
}, {
"name": "measureText",
"args": ["dataset2"]
}, {
"name": "save",
"args": []
}, {
"name": "setFillStyle",
"args": ["rgba(0,0,0,0.1)"]
}, {
"name": "setLineCap",
"args": ["butt"]
}, {
"name": "setLineDashOffset",
"args": [0]
}, {
"name": "setLineJoin",
"args": ["miter"]
}, {
"name": "setLineWidth",
"args": [3]
}, {
"name": "setStrokeStyle",
"args": ["rgba(0,0,0,0.1)"]
}, {
"name": "setLineDash",
"args": [
[]
]
}, {
"name": "strokeRect",
"args": [250, 110, 40, 12]
}, {
"name": "fillRect",
"args": [250, 110, 40, 12]
}, {
"name": "restore",
"args": []
}, {
"name": "fillText",
"args": ["dataset2", 296, 110]
}, {
"name": "beginPath",
"args": []
}, {
"name": "setLineWidth",
"args": [2]
}, {
"name": "moveTo",
"args": [296, 116]
}, {
"name": "lineTo",
"args": [376, 116]
}, {
"name": "stroke",
"args": []
}, {
"name": "measureText",
"args": ["dataset3"]
}, {
"name": "save",
"args": []
}, {
"name": "setFillStyle",
"args": ["rgba(0,0,0,0.1)"]
}, {
"name": "setLineCap",
"args": ["butt"]
}, {
"name": "setLineDashOffset",
"args": [0]
}, {
"name": "setLineJoin",
"args": ["miter"]
}, {
"name": "setLineWidth",
"args": [10]
}, {
"name": "setStrokeStyle",
"args": ["green"]
}, {
"name": "setLineDash",
"args": [
[]
]
}, {
"name": "strokeRect",
"args": [182, 132, 40, 12]
}, {
"name": "fillRect",
"args": [182, 132, 40, 12]
}, {
"name": "restore",
"args": []
}, {
"name": "fillText",
"args": ["dataset3", 228, 132]
}]);*/
});
describe('config update', function() {
it ('should update the options', function() {
var chart = acquireChart({
type: 'line',
data: {
labels: ['A', 'B', 'C', 'D'],
datasets: [{
data: [10, 20, 30, 100]
}]
},
options: {
legend: {
display: true
}
}
});
expect(chart.legend.options.display).toBe(true);
chart.options.legend.display = false;
chart.update();
expect(chart.legend.options.display).toBe(false);
});
it ('should update the associated layout item', function() {
var chart = acquireChart({
type: 'line',
data: {},
options: {
legend: {
fullWidth: true,
position: 'top',
weight: 150
}
}
});
expect(chart.legend.fullWidth).toBe(true);
expect(chart.legend.position).toBe('top');
expect(chart.legend.weight).toBe(150);
chart.options.legend.fullWidth = false;
chart.options.legend.position = 'left';
chart.options.legend.weight = 42;
chart.update();
expect(chart.legend.fullWidth).toBe(false);
expect(chart.legend.position).toBe('left');
expect(chart.legend.weight).toBe(42);
});
it ('should remove the legend if the new options are false', function() {
var chart = acquireChart({
type: 'line',
data: {
labels: ['A', 'B', 'C', 'D'],
datasets: [{
data: [10, 20, 30, 100]
}]
}
});
expect(chart.legend).not.toBe(undefined);
chart.options.legend = false;
chart.update();
expect(chart.legend).toBe(undefined);
});
it ('should create the legend if the legend options are changed to exist', function() {
var chart = acquireChart({
type: 'line',
data: {
labels: ['A', 'B', 'C', 'D'],
datasets: [{
data: [10, 20, 30, 100]
}]
},
options: {
legend: false
}
});
expect(chart.legend).toBe(undefined);
chart.options.legend = {};
chart.update();
expect(chart.legend).not.toBe(undefined);
expect(chart.legend.options).toEqual(jasmine.objectContaining(Chart.defaults.global.legend));
});
});
});

View File

@ -0,0 +1,318 @@
// Test the rectangle element
describe('Title block tests', function() {
it('Should have the correct default config', function() {
expect(Chart.defaults.global.title).toEqual({
display: false,
position: 'top',
fullWidth: true,
weight: 2000,
fontStyle: 'bold',
lineHeight: 1.2,
padding: 10,
text: ''
});
});
it('should update correctly', function() {
var chart = {};
var options = Chart.helpers.clone(Chart.defaults.global.title);
options.text = 'My title';
var title = new Chart.Title({
chart: chart,
options: options
});
var minSize = title.update(400, 200);
expect(minSize).toEqual({
width: 400,
height: 0
});
// Now we have a height since we display
title.options.display = true;
minSize = title.update(400, 200);
expect(minSize).toEqual({
width: 400,
height: 34.4
});
});
it('should update correctly when vertical', function() {
var chart = {};
var options = Chart.helpers.clone(Chart.defaults.global.title);
options.text = 'My title';
options.position = 'left';
var title = new Chart.Title({
chart: chart,
options: options
});
var minSize = title.update(200, 400);
expect(minSize).toEqual({
width: 0,
height: 400
});
// Now we have a height since we display
title.options.display = true;
minSize = title.update(200, 400);
expect(minSize).toEqual({
width: 34.4,
height: 400
});
});
it('should have the correct size when there are multiple lines of text', function() {
var chart = {};
var options = Chart.helpers.clone(Chart.defaults.global.title);
options.text = ['line1', 'line2'];
options.position = 'left';
options.display = true;
options.lineHeight = 1.5;
var title = new Chart.Title({
chart: chart,
options: options
});
var minSize = title.update(200, 400);
expect(minSize).toEqual({
width: 56,
height: 400
});
});
it('should draw correctly horizontally', function() {
var chart = {};
var context = window.createMockContext();
var options = Chart.helpers.clone(Chart.defaults.global.title);
options.text = 'My title';
var title = new Chart.Title({
chart: chart,
options: options,
ctx: context
});
title.update(400, 200);
title.draw();
expect(context.getCalls()).toEqual([]);
// Now we have a height since we display
title.options.display = true;
var minSize = title.update(400, 200);
title.top = 50;
title.left = 100;
title.bottom = title.top + minSize.height;
title.right = title.left + minSize.width;
title.draw();
expect(context.getCalls()).toEqual([{
name: 'setFillStyle',
args: ['#666']
}, {
name: 'save',
args: []
}, {
name: 'translate',
args: [300, 67.2]
}, {
name: 'rotate',
args: [0]
}, {
name: 'fillText',
args: ['My title', 0, 0, 400]
}, {
name: 'restore',
args: []
}]);
});
it ('should draw correctly vertically', function() {
var chart = {};
var context = window.createMockContext();
var options = Chart.helpers.clone(Chart.defaults.global.title);
options.text = 'My title';
options.position = 'left';
var title = new Chart.Title({
chart: chart,
options: options,
ctx: context
});
title.update(200, 400);
title.draw();
expect(context.getCalls()).toEqual([]);
// Now we have a height since we display
title.options.display = true;
var minSize = title.update(200, 400);
title.top = 50;
title.left = 100;
title.bottom = title.top + minSize.height;
title.right = title.left + minSize.width;
title.draw();
expect(context.getCalls()).toEqual([{
name: 'setFillStyle',
args: ['#666']
}, {
name: 'save',
args: []
}, {
name: 'translate',
args: [117.2, 250]
}, {
name: 'rotate',
args: [-0.5 * Math.PI]
}, {
name: 'fillText',
args: ['My title', 0, 0, 400]
}, {
name: 'restore',
args: []
}]);
// Rotation is other way on right side
title.options.position = 'right';
// Reset call tracker
context.resetCalls();
minSize = title.update(200, 400);
title.top = 50;
title.left = 100;
title.bottom = title.top + minSize.height;
title.right = title.left + minSize.width;
title.draw();
expect(context.getCalls()).toEqual([{
name: 'setFillStyle',
args: ['#666']
}, {
name: 'save',
args: []
}, {
name: 'translate',
args: [117.2, 250]
}, {
name: 'rotate',
args: [0.5 * Math.PI]
}, {
name: 'fillText',
args: ['My title', 0, 0, 400]
}, {
name: 'restore',
args: []
}]);
});
describe('config update', function() {
it ('should update the options', function() {
var chart = acquireChart({
type: 'line',
data: {
labels: ['A', 'B', 'C', 'D'],
datasets: [{
data: [10, 20, 30, 100]
}]
},
options: {
title: {
display: true
}
}
});
expect(chart.titleBlock.options.display).toBe(true);
chart.options.title.display = false;
chart.update();
expect(chart.titleBlock.options.display).toBe(false);
});
it ('should update the associated layout item', function() {
var chart = acquireChart({
type: 'line',
data: {},
options: {
title: {
fullWidth: true,
position: 'top',
weight: 150
}
}
});
expect(chart.titleBlock.fullWidth).toBe(true);
expect(chart.titleBlock.position).toBe('top');
expect(chart.titleBlock.weight).toBe(150);
chart.options.title.fullWidth = false;
chart.options.title.position = 'left';
chart.options.title.weight = 42;
chart.update();
expect(chart.titleBlock.fullWidth).toBe(false);
expect(chart.titleBlock.position).toBe('left');
expect(chart.titleBlock.weight).toBe(42);
});
it ('should remove the title if the new options are false', function() {
var chart = acquireChart({
type: 'line',
data: {
labels: ['A', 'B', 'C', 'D'],
datasets: [{
data: [10, 20, 30, 100]
}]
}
});
expect(chart.titleBlock).not.toBe(undefined);
chart.options.title = false;
chart.update();
expect(chart.titleBlock).toBe(undefined);
});
it ('should create the title if the title options are changed to exist', function() {
var chart = acquireChart({
type: 'line',
data: {
labels: ['A', 'B', 'C', 'D'],
datasets: [{
data: [10, 20, 30, 100]
}]
},
options: {
title: false
}
});
expect(chart.titleBlock).toBe(undefined);
chart.options.title = {};
chart.update();
expect(chart.titleBlock).not.toBe(undefined);
expect(chart.titleBlock.options).toEqual(jasmine.objectContaining(Chart.defaults.global.title));
});
});
});

View File

@ -0,0 +1,390 @@
// Test the category scale
describe('Category scale tests', function() {
it('Should register the constructor with the scale service', function() {
var Constructor = Chart.scaleService.getScaleConstructor('category');
expect(Constructor).not.toBe(undefined);
expect(typeof Constructor).toBe('function');
});
it('Should have the correct default config', function() {
var defaultConfig = Chart.scaleService.getScaleDefaults('category');
expect(defaultConfig).toEqual({
display: true,
gridLines: {
color: 'rgba(0, 0, 0, 0.1)',
drawBorder: true,
drawOnChartArea: true,
drawTicks: true, // draw ticks extending towards the label
tickMarkLength: 10,
lineWidth: 1,
offsetGridLines: false,
display: true,
zeroLineColor: 'rgba(0,0,0,0.25)',
zeroLineWidth: 1,
zeroLineBorderDash: [],
zeroLineBorderDashOffset: 0.0,
borderDash: [],
borderDashOffset: 0.0
},
position: 'bottom',
offset: false,
scaleLabel: Chart.defaults.scale.scaleLabel,
ticks: {
beginAtZero: false,
minRotation: 0,
maxRotation: 50,
mirror: false,
padding: 0,
reverse: false,
display: true,
callback: defaultConfig.ticks.callback, // make this nicer, then check explicitly below
autoSkip: true,
autoSkipPadding: 0,
labelOffset: 0,
minor: {},
major: {},
}
});
// Is this actually a function
expect(defaultConfig.ticks.callback).toEqual(jasmine.any(Function));
});
it('Should generate ticks from the data labels', function() {
var scaleID = 'myScale';
var mockData = {
datasets: [{
yAxisID: scaleID,
data: [10, 5, 0, 25, 78]
}],
labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']
};
var config = Chart.helpers.clone(Chart.scaleService.getScaleDefaults('category'));
var Constructor = Chart.scaleService.getScaleConstructor('category');
var scale = new Constructor({
ctx: {},
options: config,
chart: {
data: mockData
},
id: scaleID
});
scale.determineDataLimits();
scale.buildTicks();
expect(scale.ticks).toEqual(mockData.labels);
});
it('Should generate ticks from the data xLabels', function() {
var scaleID = 'myScale';
var mockData = {
datasets: [{
yAxisID: scaleID,
data: [10, 5, 0, 25, 78]
}],
xLabels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']
};
var config = Chart.helpers.clone(Chart.scaleService.getScaleDefaults('category'));
var Constructor = Chart.scaleService.getScaleConstructor('category');
var scale = new Constructor({
ctx: {},
options: config,
chart: {
data: mockData
},
id: scaleID
});
scale.determineDataLimits();
scale.buildTicks();
expect(scale.ticks).toEqual(mockData.xLabels);
});
it('Should generate ticks from the data yLabels', function() {
var scaleID = 'myScale';
var mockData = {
datasets: [{
yAxisID: scaleID,
data: [10, 5, 0, 25, 78]
}],
yLabels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']
};
var config = Chart.helpers.clone(Chart.scaleService.getScaleDefaults('category'));
config.position = 'left'; // y axis
var Constructor = Chart.scaleService.getScaleConstructor('category');
var scale = new Constructor({
ctx: {},
options: config,
chart: {
data: mockData
},
id: scaleID
});
scale.determineDataLimits();
scale.buildTicks();
expect(scale.ticks).toEqual(mockData.yLabels);
});
it('Should generate ticks from the axis labels', function() {
var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'];
var chart = window.acquireChart({
type: 'line',
data: {
data: [10, 5, 0, 25, 78]
},
options: {
scales: {
xAxes: [{
id: 'x',
type: 'category',
labels: labels
}]
}
}
});
var scale = chart.scales.x;
expect(scale.ticks).toEqual(labels);
});
it ('should get the correct label for the index', function() {
var scaleID = 'myScale';
var mockData = {
datasets: [{
yAxisID: scaleID,
data: [10, 5, 0, 25, 78]
}],
labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']
};
var config = Chart.helpers.clone(Chart.scaleService.getScaleDefaults('category'));
var Constructor = Chart.scaleService.getScaleConstructor('category');
var scale = new Constructor({
ctx: {},
options: config,
chart: {
data: mockData
},
id: scaleID
});
scale.determineDataLimits();
scale.buildTicks();
expect(scale.getLabelForIndex(1)).toBe('tick2');
});
it ('Should get the correct pixel for a value when horizontal', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
xAxisID: 'xScale0',
yAxisID: 'yScale0',
data: [10, 5, 0, 25, 78]
}],
labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick_last']
},
options: {
scales: {
xAxes: [{
id: 'xScale0',
type: 'category',
position: 'bottom'
}],
yAxes: [{
id: 'yScale0',
type: 'linear'
}]
}
}
});
var xScale = chart.scales.xScale0;
expect(xScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(23 + 6); // plus lineHeight
expect(xScale.getValueForPixel(23)).toBe(0);
expect(xScale.getPixelForValue(0, 4, 0)).toBeCloseToPixel(487);
expect(xScale.getValueForPixel(487)).toBe(4);
xScale.options.offset = true;
chart.update();
expect(xScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(69 + 6); // plus lineHeight
expect(xScale.getValueForPixel(69)).toBe(0);
expect(xScale.getPixelForValue(0, 4, 0)).toBeCloseToPixel(441);
expect(xScale.getValueForPixel(397)).toBe(4);
});
it ('Should get the correct pixel for a value when there are repeated labels', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
xAxisID: 'xScale0',
yAxisID: 'yScale0',
data: [10, 5, 0, 25, 78]
}],
labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick_last']
},
options: {
scales: {
xAxes: [{
id: 'xScale0',
type: 'category',
position: 'bottom'
}],
yAxes: [{
id: 'yScale0',
type: 'linear'
}]
}
}
});
var xScale = chart.scales.xScale0;
expect(xScale.getPixelForValue('tick_1', 0, 0)).toBeCloseToPixel(23 + 6); // plus lineHeight
expect(xScale.getPixelForValue('tick_1', 1, 0)).toBeCloseToPixel(143);
});
it ('Should get the correct pixel for a value when horizontal and zoomed', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
xAxisID: 'xScale0',
yAxisID: 'yScale0',
data: [10, 5, 0, 25, 78]
}],
labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick_last']
},
options: {
scales: {
xAxes: [{
id: 'xScale0',
type: 'category',
position: 'bottom',
ticks: {
min: 'tick2',
max: 'tick4'
}
}],
yAxes: [{
id: 'yScale0',
type: 'linear'
}]
}
}
});
var xScale = chart.scales.xScale0;
expect(xScale.getPixelForValue(0, 1, 0)).toBeCloseToPixel(23 + 6); // plus lineHeight
expect(xScale.getPixelForValue(0, 3, 0)).toBeCloseToPixel(496);
xScale.options.offset = true;
chart.update();
expect(xScale.getPixelForValue(0, 1, 0)).toBeCloseToPixel(102 + 6); // plus lineHeight
expect(xScale.getPixelForValue(0, 3, 0)).toBeCloseToPixel(417);
});
it ('should get the correct pixel for a value when vertical', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
xAxisID: 'xScale0',
yAxisID: 'yScale0',
data: ['3', '5', '1', '4', '2']
}],
labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'],
yLabels: ['1', '2', '3', '4', '5']
},
options: {
scales: {
xAxes: [{
id: 'xScale0',
type: 'category',
position: 'bottom',
}],
yAxes: [{
id: 'yScale0',
type: 'category',
position: 'left'
}]
}
}
});
var yScale = chart.scales.yScale0;
expect(yScale.getPixelForValue(0, 0, 0)).toBe(32);
expect(yScale.getValueForPixel(32)).toBe(0);
expect(yScale.getPixelForValue(0, 4, 0)).toBe(484);
expect(yScale.getValueForPixel(484)).toBe(4);
yScale.options.offset = true;
chart.update();
expect(yScale.getPixelForValue(0, 0, 0)).toBe(77);
expect(yScale.getValueForPixel(77)).toBe(0);
expect(yScale.getPixelForValue(0, 4, 0)).toBe(439);
expect(yScale.getValueForPixel(439)).toBe(4);
});
it ('should get the correct pixel for a value when vertical and zoomed', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
xAxisID: 'xScale0',
yAxisID: 'yScale0',
data: ['3', '5', '1', '4', '2']
}],
labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'],
yLabels: ['1', '2', '3', '4', '5']
},
options: {
scales: {
xAxes: [{
id: 'xScale0',
type: 'category',
position: 'bottom',
}],
yAxes: [{
id: 'yScale0',
type: 'category',
position: 'left',
ticks: {
min: '2',
max: '4'
}
}]
}
}
});
var yScale = chart.scales.yScale0;
expect(yScale.getPixelForValue(0, 1, 0)).toBe(32);
expect(yScale.getPixelForValue(0, 3, 0)).toBe(484);
yScale.options.offset = true;
chart.update();
expect(yScale.getPixelForValue(0, 1, 0)).toBe(107);
expect(yScale.getPixelForValue(0, 3, 0)).toBe(409);
});
});

View File

@ -0,0 +1,908 @@
describe('Linear Scale', function() {
it('Should register the constructor with the scale service', function() {
var Constructor = Chart.scaleService.getScaleConstructor('linear');
expect(Constructor).not.toBe(undefined);
expect(typeof Constructor).toBe('function');
});
it('Should have the correct default config', function() {
var defaultConfig = Chart.scaleService.getScaleDefaults('linear');
expect(defaultConfig).toEqual({
display: true,
gridLines: {
color: 'rgba(0, 0, 0, 0.1)',
drawBorder: true,
drawOnChartArea: true,
drawTicks: true, // draw ticks extending towards the label
tickMarkLength: 10,
lineWidth: 1,
offsetGridLines: false,
display: true,
zeroLineColor: 'rgba(0,0,0,0.25)',
zeroLineWidth: 1,
zeroLineBorderDash: [],
zeroLineBorderDashOffset: 0.0,
borderDash: [],
borderDashOffset: 0.0
},
position: 'left',
offset: false,
scaleLabel: Chart.defaults.scale.scaleLabel,
ticks: {
beginAtZero: false,
minRotation: 0,
maxRotation: 50,
mirror: false,
padding: 0,
reverse: false,
display: true,
callback: defaultConfig.ticks.callback, // make this work nicer, then check below
autoSkip: true,
autoSkipPadding: 0,
labelOffset: 0,
minor: {},
major: {},
}
});
expect(defaultConfig.ticks.callback).toEqual(jasmine.any(Function));
});
it('Should correctly determine the max & min data values', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
yAxisID: 'yScale0',
data: [10, 5, 0, -5, 78, -100]
}, {
yAxisID: 'yScale1',
data: [-1000, 1000],
}, {
yAxisID: 'yScale0',
data: [150]
}],
labels: ['a', 'b', 'c', 'd', 'e', 'f']
},
options: {
scales: {
yAxes: [{
id: 'yScale0',
type: 'linear'
}, {
id: 'yScale1',
type: 'linear'
}]
}
}
});
expect(chart.scales.yScale0).not.toEqual(undefined); // must construct
expect(chart.scales.yScale0.min).toBe(-100);
expect(chart.scales.yScale0.max).toBe(150);
});
it('Should correctly determine the max & min of string data values', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
yAxisID: 'yScale0',
data: ['10', '5', '0', '-5', '78', '-100']
}, {
yAxisID: 'yScale1',
data: ['-1000', '1000'],
}, {
yAxisID: 'yScale0',
data: ['150']
}],
labels: ['a', 'b', 'c', 'd', 'e', 'f']
},
options: {
scales: {
yAxes: [{
id: 'yScale0',
type: 'linear'
}, {
id: 'yScale1',
type: 'linear'
}]
}
}
});
expect(chart.scales.yScale0).not.toEqual(undefined); // must construct
expect(chart.scales.yScale0.min).toBe(-100);
expect(chart.scales.yScale0.max).toBe(150);
});
it('Should correctly determine the max & min when no values provided and suggested minimum and maximum are set', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
yAxisID: 'yScale0',
data: []
}],
labels: ['a', 'b', 'c', 'd', 'e', 'f']
},
options: {
scales: {
yAxes: [{
id: 'yScale0',
type: 'linear',
ticks: {
suggestedMin: -10,
suggestedMax: 15
}
}]
}
}
});
expect(chart.scales.yScale0).not.toEqual(undefined); // must construct
expect(chart.scales.yScale0.min).toBe(-10);
expect(chart.scales.yScale0.max).toBe(15);
});
it('Should correctly determine the max & min data values ignoring hidden datasets', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
yAxisID: 'yScale0',
data: ['10', '5', '0', '-5', '78', '-100']
}, {
yAxisID: 'yScale1',
data: ['-1000', '1000'],
}, {
yAxisID: 'yScale0',
data: ['150'],
hidden: true
}],
labels: ['a', 'b', 'c', 'd', 'e', 'f']
},
options: {
scales: {
yAxes: [{
id: 'yScale0',
type: 'linear'
}, {
id: 'yScale1',
type: 'linear'
}]
}
}
});
expect(chart.scales.yScale0).not.toEqual(undefined); // must construct
expect(chart.scales.yScale0.min).toBe(-100);
expect(chart.scales.yScale0.max).toBe(80);
});
it('Should correctly determine the max & min data values ignoring data that is NaN', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
yAxisID: 'yScale0',
data: [null, 90, NaN, undefined, 45, 30, Infinity, -Infinity]
}],
labels: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
},
options: {
scales: {
yAxes: [{
id: 'yScale0',
type: 'linear'
}]
}
}
});
expect(chart.scales.yScale0.min).toBe(30);
expect(chart.scales.yScale0.max).toBe(90);
// Scale is now stacked
chart.scales.yScale0.options.stacked = true;
chart.update();
expect(chart.scales.yScale0.min).toBe(0);
expect(chart.scales.yScale0.max).toBe(90);
});
it('Should correctly determine the max & min for scatter data', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
xAxisID: 'xScale0',
yAxisID: 'yScale0',
data: [{
x: 10,
y: 100
}, {
x: -10,
y: 0
}, {
x: 0,
y: 0
}, {
x: 99,
y: 7
}]
}],
},
options: {
scales: {
xAxes: [{
id: 'xScale0',
type: 'linear',
position: 'bottom'
}],
yAxes: [{
id: 'yScale0',
type: 'linear'
}]
}
}
});
chart.update();
expect(chart.scales.xScale0.min).toBe(-20);
expect(chart.scales.xScale0.max).toBe(100);
expect(chart.scales.yScale0.min).toBe(0);
expect(chart.scales.yScale0.max).toBe(100);
});
it('Should correctly get the label for the given index', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
xAxisID: 'xScale0',
yAxisID: 'yScale0',
data: [{
x: 10,
y: 100
}, {
x: -10,
y: 0
}, {
x: 0,
y: 0
}, {
x: 99,
y: 7
}]
}],
},
options: {
scales: {
xAxes: [{
id: 'xScale0',
type: 'linear',
position: 'bottom'
}],
yAxes: [{
id: 'yScale0',
type: 'linear'
}]
}
}
});
chart.update();
expect(chart.scales.yScale0.getLabelForIndex(3, 0)).toBe(7);
});
it('Should correctly determine the min and max data values when stacked mode is turned on', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
yAxisID: 'yScale0',
data: [10, 5, 0, -5, 78, -100],
type: 'bar'
}, {
yAxisID: 'yScale1',
data: [-1000, 1000],
}, {
yAxisID: 'yScale0',
data: [150, 0, 0, -100, -10, 9],
type: 'bar'
}, {
yAxisID: 'yScale0',
data: [10, 10, 10, 10, 10, 10],
type: 'line'
}],
labels: ['a', 'b', 'c', 'd', 'e', 'f']
},
options: {
scales: {
yAxes: [{
id: 'yScale0',
type: 'linear',
stacked: true
}, {
id: 'yScale1',
type: 'linear'
}]
}
}
});
chart.update();
expect(chart.scales.yScale0.min).toBe(-150);
expect(chart.scales.yScale0.max).toBe(200);
});
it('Should correctly determine the min and max data values when stacked mode is turned on and there are hidden datasets', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
yAxisID: 'yScale0',
data: [10, 5, 0, -5, 78, -100],
}, {
yAxisID: 'yScale1',
data: [-1000, 1000],
}, {
yAxisID: 'yScale0',
data: [150, 0, 0, -100, -10, 9],
}, {
yAxisID: 'yScale0',
data: [10, 20, 30, 40, 50, 60],
hidden: true
}],
labels: ['a', 'b', 'c', 'd', 'e', 'f']
},
options: {
scales: {
yAxes: [{
id: 'yScale0',
type: 'linear',
stacked: true
}, {
id: 'yScale1',
type: 'linear'
}]
}
}
});
chart.update();
expect(chart.scales.yScale0.min).toBe(-150);
expect(chart.scales.yScale0.max).toBe(200);
});
it('Should correctly determine the min and max data values when stacked mode is turned on there are multiple types of datasets', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
yAxisID: 'yScale0',
type: 'bar',
data: [10, 5, 0, -5, 78, -100]
}, {
type: 'line',
data: [10, 10, 10, 10, 10, 10],
}, {
type: 'bar',
data: [150, 0, 0, -100, -10, 9]
}],
labels: ['a', 'b', 'c', 'd', 'e', 'f']
},
options: {
scales: {
yAxes: [{
id: 'yScale0',
type: 'linear',
stacked: true
}]
}
}
});
chart.scales.yScale0.determineDataLimits();
expect(chart.scales.yScale0.min).toBe(-105);
expect(chart.scales.yScale0.max).toBe(160);
});
it('Should ensure that the scale has a max and min that are not equal', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [],
labels: ['a', 'b', 'c', 'd', 'e', 'f']
},
options: {
scales: {
yAxes: [{
id: 'yScale0',
type: 'linear'
}]
}
}
});
expect(chart.scales.yScale0).not.toEqual(undefined); // must construct
expect(chart.scales.yScale0.min).toBe(-1);
expect(chart.scales.yScale0.max).toBe(1);
});
it('Should ensure that the scale has a max and min that are not equal when beginAtZero is set', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [],
labels: ['a', 'b', 'c', 'd', 'e', 'f']
},
options: {
scales: {
yAxes: [{
id: 'yScale0',
type: 'linear',
ticks: {
beginAtZero: true
}
}]
}
}
});
expect(chart.scales.yScale0).not.toEqual(undefined); // must construct
expect(chart.scales.yScale0.min).toBe(0);
expect(chart.scales.yScale0.max).toBe(1);
});
it('Should use the suggestedMin and suggestedMax options', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
yAxisID: 'yScale0',
data: [1, 1, 1, 2, 1, 0]
}],
labels: ['a', 'b', 'c', 'd', 'e', 'f']
},
options: {
scales: {
yAxes: [{
id: 'yScale0',
type: 'linear',
ticks: {
suggestedMax: 10,
suggestedMin: -10
}
}]
}
}
});
expect(chart.scales.yScale0).not.toEqual(undefined); // must construct
expect(chart.scales.yScale0.min).toBe(-10);
expect(chart.scales.yScale0.max).toBe(10);
});
it('Should use the min and max options', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
yAxisID: 'yScale0',
data: [1, 1, 1, 2, 1, 0]
}],
labels: ['a', 'b', 'c', 'd', 'e', 'f']
},
options: {
scales: {
yAxes: [{
id: 'yScale0',
type: 'linear',
ticks: {
max: 1010,
min: -1010
}
}]
}
}
});
expect(chart.scales.yScale0).not.toEqual(undefined); // must construct
expect(chart.scales.yScale0.min).toBe(-1010);
expect(chart.scales.yScale0.max).toBe(1010);
expect(chart.scales.yScale0.ticks[0]).toBe('1010');
expect(chart.scales.yScale0.ticks[chart.scales.yScale0.ticks.length - 1]).toBe('-1010');
});
it('Should use min, max and stepSize to create fixed spaced ticks', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
yAxisID: 'yScale0',
data: [10, 3, 6, 8, 3, 1]
}],
labels: ['a', 'b', 'c', 'd', 'e', 'f']
},
options: {
scales: {
yAxes: [{
id: 'yScale0',
type: 'linear',
ticks: {
min: 1,
max: 11,
stepSize: 2
}
}]
}
}
});
expect(chart.scales.yScale0).not.toEqual(undefined); // must construct
expect(chart.scales.yScale0.min).toBe(1);
expect(chart.scales.yScale0.max).toBe(11);
expect(chart.scales.yScale0.ticks).toEqual(['11', '9', '7', '5', '3', '1']);
});
it('should forcibly include 0 in the range if the beginAtZero option is used', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
yAxisID: 'yScale0',
data: [20, 30, 40, 50]
}],
labels: ['a', 'b', 'c', 'd']
},
options: {
scales: {
yAxes: [{
id: 'yScale0',
type: 'linear',
}]
}
}
});
expect(chart.scales.yScale0).not.toEqual(undefined); // must construct
expect(chart.scales.yScale0.ticks).toEqual(['50', '45', '40', '35', '30', '25', '20']);
chart.scales.yScale0.options.ticks.beginAtZero = true;
chart.update();
expect(chart.scales.yScale0.ticks).toEqual(['50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']);
chart.data.datasets[0].data = [-20, -30, -40, -50];
chart.update();
expect(chart.scales.yScale0.ticks).toEqual(['0', '-5', '-10', '-15', '-20', '-25', '-30', '-35', '-40', '-45', '-50']);
chart.scales.yScale0.options.ticks.beginAtZero = false;
chart.update();
expect(chart.scales.yScale0.ticks).toEqual(['-20', '-25', '-30', '-35', '-40', '-45', '-50']);
});
it('Should generate tick marks in the correct order in reversed mode', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
yAxisID: 'yScale0',
data: [10, 5, 0, 25, 78]
}],
labels: ['a', 'b', 'c', 'd']
},
options: {
scales: {
yAxes: [{
id: 'yScale0',
type: 'linear',
ticks: {
reverse: true
}
}]
}
}
});
expect(chart.scales.yScale0.ticks).toEqual(['0', '10', '20', '30', '40', '50', '60', '70', '80']);
expect(chart.scales.yScale0.start).toBe(80);
expect(chart.scales.yScale0.end).toBe(0);
});
it('should use the correct number of decimal places in the default format function', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
yAxisID: 'yScale0',
data: [0.06, 0.005, 0, 0.025, 0.0078]
}],
labels: ['a', 'b', 'c', 'd']
},
options: {
scales: {
yAxes: [{
id: 'yScale0',
type: 'linear',
}]
}
}
});
expect(chart.scales.yScale0.ticks).toEqual(['0.06', '0.05', '0.04', '0.03', '0.02', '0.01', '0']);
});
it('Should build labels using the user supplied callback', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
yAxisID: 'yScale0',
data: [10, 5, 0, 25, 78]
}],
labels: ['a', 'b', 'c', 'd']
},
options: {
scales: {
yAxes: [{
id: 'yScale0',
type: 'linear',
ticks: {
callback: function(value, index) {
return index.toString();
}
}
}]
}
}
});
// Just the index
expect(chart.scales.yScale0.ticks).toEqual(['0', '1', '2', '3', '4', '5', '6', '7', '8']);
});
it('Should get the correct pixel value for a point', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
xAxisID: 'xScale0',
yAxisID: 'yScale0',
data: []
}],
},
options: {
scales: {
xAxes: [{
id: 'xScale0',
type: 'linear',
position: 'bottom'
}],
yAxes: [{
id: 'yScale0',
type: 'linear'
}]
}
}
});
var xScale = chart.scales.xScale0;
expect(xScale.getPixelForValue(1, 0, 0)).toBeCloseToPixel(501); // right - paddingRight
expect(xScale.getPixelForValue(-1, 0, 0)).toBeCloseToPixel(31 + 6); // left + paddingLeft + lineSpace
expect(xScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(266 + 6 / 2); // halfway*/
expect(xScale.getValueForPixel(501)).toBeCloseTo(1, 1e-2);
expect(xScale.getValueForPixel(31)).toBeCloseTo(-1, 1e-2);
expect(xScale.getValueForPixel(266)).toBeCloseTo(0, 1e-2);
var yScale = chart.scales.yScale0;
expect(yScale.getPixelForValue(1, 0, 0)).toBeCloseToPixel(32); // right - paddingRight
expect(yScale.getPixelForValue(-1, 0, 0)).toBeCloseToPixel(484); // left + paddingLeft
expect(yScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(258); // halfway*/
expect(yScale.getValueForPixel(32)).toBe(1);
expect(yScale.getValueForPixel(484)).toBe(-1);
expect(yScale.getValueForPixel(258)).toBe(0);
});
it('should fit correctly', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
xAxisID: 'xScale0',
yAxisID: 'yScale0',
data: [{
x: 10,
y: 100
}, {
x: -10,
y: 0
}, {
x: 0,
y: 0
}, {
x: 99,
y: 7
}]
}],
},
options: {
scales: {
xAxes: [{
id: 'xScale0',
type: 'linear',
position: 'bottom'
}],
yAxes: [{
id: 'yScale0',
type: 'linear'
}]
}
}
});
var xScale = chart.scales.xScale0;
expect(xScale.paddingTop).toBeCloseToPixel(0);
expect(xScale.paddingBottom).toBeCloseToPixel(0);
expect(xScale.paddingLeft).toBeCloseToPixel(0);
expect(xScale.paddingRight).toBeCloseToPixel(0);
expect(xScale.width).toBeCloseToPixel(468 - 6); // minus lineSpace
expect(xScale.height).toBeCloseToPixel(28);
var yScale = chart.scales.yScale0;
expect(yScale.paddingTop).toBeCloseToPixel(0);
expect(yScale.paddingBottom).toBeCloseToPixel(0);
expect(yScale.paddingLeft).toBeCloseToPixel(0);
expect(yScale.paddingRight).toBeCloseToPixel(0);
expect(yScale.width).toBeCloseToPixel(30 + 6); // plus lineSpace
expect(yScale.height).toBeCloseToPixel(452);
// Extra size when scale label showing
xScale.options.scaleLabel.display = true;
yScale.options.scaleLabel.display = true;
chart.update();
expect(xScale.paddingTop).toBeCloseToPixel(0);
expect(xScale.paddingBottom).toBeCloseToPixel(0);
expect(xScale.paddingLeft).toBeCloseToPixel(0);
expect(xScale.paddingRight).toBeCloseToPixel(0);
expect(xScale.width).toBeCloseToPixel(440);
expect(xScale.height).toBeCloseToPixel(50);
expect(yScale.paddingTop).toBeCloseToPixel(0);
expect(yScale.paddingBottom).toBeCloseToPixel(0);
expect(yScale.paddingLeft).toBeCloseToPixel(0);
expect(yScale.paddingRight).toBeCloseToPixel(0);
expect(yScale.width).toBeCloseToPixel(58);
expect(yScale.height).toBeCloseToPixel(430);
});
it('should fit correctly when display is turned off', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
xAxisID: 'xScale0',
yAxisID: 'yScale0',
data: [{
x: 10,
y: 100
}, {
x: -10,
y: 0
}, {
x: 0,
y: 0
}, {
x: 99,
y: 7
}]
}],
},
options: {
scales: {
xAxes: [{
id: 'xScale0',
type: 'linear',
position: 'bottom'
}],
yAxes: [{
id: 'yScale0',
type: 'linear',
gridLines: {
drawTicks: false,
drawBorder: false
},
scaleLabel: {
display: false,
lineHeight: 1.2
},
ticks: {
display: false,
padding: 0
}
}]
}
}
});
var yScale = chart.scales.yScale0;
expect(yScale.width).toBeCloseToPixel(0);
});
it('max and min value should be valid and finite when charts datasets are hidden', function() {
var barData = {
labels: ['S1', 'S2', 'S3'],
datasets: [{
label: 'Closed',
backgroundColor: '#382765',
data: [2500, 2000, 1500]
}, {
label: 'In Progress',
backgroundColor: '#7BC225',
data: [1000, 2000, 1500]
}, {
label: 'Assigned',
backgroundColor: '#ffC225',
data: [1000, 2000, 1500]
}]
};
var chart = window.acquireChart({
type: 'horizontalBar',
data: barData,
options: {
scales: {
xAxes: [{
stacked: true
}],
yAxes: [{
stacked: true
}]
}
}
});
barData.datasets.forEach(function(data, index) {
var meta = chart.getDatasetMeta(index);
meta.hidden = true;
chart.update();
});
expect(chart.scales['x-axis-0'].min).toEqual(0);
expect(chart.scales['x-axis-0'].max).toEqual(1);
});
it('max and min value should be valid when min is set and all datasets are hidden', function() {
var barData = {
labels: ['S1', 'S2', 'S3'],
datasets: [{
label: 'dataset 1',
backgroundColor: '#382765',
data: [2500, 2000, 1500],
hidden: true,
}]
};
var chart = window.acquireChart({
type: 'horizontalBar',
data: barData,
options: {
scales: {
xAxes: [{
ticks: {
min: 20
}
}]
}
}
});
expect(chart.scales['x-axis-0'].min).toEqual(20);
expect(chart.scales['x-axis-0'].max).toEqual(21);
});
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,447 @@
// Tests for the radial linear scale used by the polar area and radar charts
describe('Test the radial linear scale', function() {
it('Should register the constructor with the scale service', function() {
var Constructor = Chart.scaleService.getScaleConstructor('radialLinear');
expect(Constructor).not.toBe(undefined);
expect(typeof Constructor).toBe('function');
});
it('Should have the correct default config', function() {
var defaultConfig = Chart.scaleService.getScaleDefaults('radialLinear');
expect(defaultConfig).toEqual({
angleLines: {
display: true,
color: 'rgba(0, 0, 0, 0.1)',
lineWidth: 1
},
animate: true,
display: true,
gridLines: {
circular: false,
color: 'rgba(0, 0, 0, 0.1)',
drawBorder: true,
drawOnChartArea: true,
drawTicks: true,
tickMarkLength: 10,
lineWidth: 1,
offsetGridLines: false,
display: true,
zeroLineColor: 'rgba(0,0,0,0.25)',
zeroLineWidth: 1,
zeroLineBorderDash: [],
zeroLineBorderDashOffset: 0.0,
borderDash: [],
borderDashOffset: 0.0
},
pointLabels: {
display: true,
fontSize: 10,
callback: defaultConfig.pointLabels.callback, // make this nicer, then check explicitly below
},
position: 'chartArea',
offset: false,
scaleLabel: Chart.defaults.scale.scaleLabel,
ticks: {
backdropColor: 'rgba(255,255,255,0.75)',
backdropPaddingY: 2,
backdropPaddingX: 2,
beginAtZero: false,
minRotation: 0,
maxRotation: 50,
mirror: false,
padding: 0,
reverse: false,
showLabelBackdrop: true,
display: true,
callback: defaultConfig.ticks.callback, // make this nicer, then check explicitly below
autoSkip: true,
autoSkipPadding: 0,
labelOffset: 0,
minor: {},
major: {},
},
});
// Is this actually a function
expect(defaultConfig.ticks.callback).toEqual(jasmine.any(Function));
expect(defaultConfig.pointLabels.callback).toEqual(jasmine.any(Function));
});
it('Should correctly determine the max & min data values', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [10, 5, 0, -5, 78, -100]
}, {
data: [150]
}],
labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6']
},
options: {
scales: {}
}
});
expect(chart.scale.min).toBe(-100);
expect(chart.scale.max).toBe(150);
});
it('Should correctly determine the max & min of string data values', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: ['10', '5', '0', '-5', '78', '-100']
}, {
data: ['150']
}],
labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6']
},
options: {
scales: {}
}
});
expect(chart.scale.min).toBe(-100);
expect(chart.scale.max).toBe(150);
});
it('Should correctly determine the max & min data values when there are hidden datasets', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: ['10', '5', '0', '-5', '78', '-100']
}, {
data: ['150']
}, {
data: [1000],
hidden: true
}],
labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6']
},
options: {
scales: {}
}
});
expect(chart.scale.min).toBe(-100);
expect(chart.scale.max).toBe(150);
});
it('Should correctly determine the max & min data values when there is NaN data', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [50, 60, NaN, 70, null, undefined, Infinity, -Infinity]
}],
labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6', 'label7', 'label8']
},
options: {
scales: {}
}
});
expect(chart.scale.min).toBe(50);
expect(chart.scale.max).toBe(70);
});
it('Should ensure that the scale has a max and min that are not equal', function() {
var scaleID = 'myScale';
var mockData = {
datasets: [],
labels: []
};
var mockContext = window.createMockContext();
var Constructor = Chart.scaleService.getScaleConstructor('radialLinear');
var scale = new Constructor({
ctx: mockContext,
options: Chart.scaleService.getScaleDefaults('radialLinear'), // use default config for scale
chart: {
data: mockData
},
id: scaleID,
});
scale.update(200, 300);
expect(scale.min).toBe(-1);
expect(scale.max).toBe(1);
});
it('Should use the suggestedMin and suggestedMax options', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [1, 1, 1, 2, 1, 0]
}],
labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6']
},
options: {
scale: {
ticks: {
suggestedMin: -10,
suggestedMax: 10
}
}
}
});
expect(chart.scale.min).toBe(-10);
expect(chart.scale.max).toBe(10);
});
it('Should use the min and max options', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [1, 1, 1, 2, 1, 0]
}],
labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6']
},
options: {
scale: {
ticks: {
min: -1010,
max: 1010
}
}
}
});
expect(chart.scale.min).toBe(-1010);
expect(chart.scale.max).toBe(1010);
expect(chart.scale.ticks).toEqual(['-1010', '-1000', '-500', '0', '500', '1000', '1010']);
});
it('should forcibly include 0 in the range if the beginAtZero option is used', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [20, 30, 40, 50]
}],
labels: ['label1', 'label2', 'label3', 'label4']
},
options: {
scale: {
ticks: {
beginAtZero: false
}
}
}
});
expect(chart.scale.ticks).toEqual(['20', '25', '30', '35', '40', '45', '50']);
chart.scale.options.ticks.beginAtZero = true;
chart.update();
expect(chart.scale.ticks).toEqual(['0', '5', '10', '15', '20', '25', '30', '35', '40', '45', '50']);
chart.data.datasets[0].data = [-20, -30, -40, -50];
chart.update();
expect(chart.scale.ticks).toEqual(['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']);
chart.scale.options.ticks.beginAtZero = false;
chart.update();
expect(chart.scale.ticks).toEqual(['-50', '-45', '-40', '-35', '-30', '-25', '-20']);
});
it('Should generate tick marks in the correct order in reversed mode', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [10, 5, 0, 25, 78]
}],
labels: ['label1', 'label2', 'label3', 'label4', 'label5']
},
options: {
scale: {
ticks: {
reverse: true
}
}
}
});
expect(chart.scale.ticks).toEqual(['80', '70', '60', '50', '40', '30', '20', '10', '0']);
expect(chart.scale.start).toBe(80);
expect(chart.scale.end).toBe(0);
});
it('Should build labels using the user supplied callback', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [10, 5, 0, 25, 78]
}],
labels: ['label1', 'label2', 'label3', 'label4', 'label5']
},
options: {
scale: {
ticks: {
callback: function(value, index) {
return index.toString();
}
}
}
}
});
expect(chart.scale.ticks).toEqual(['0', '1', '2', '3', '4', '5', '6', '7', '8']);
expect(chart.scale.pointLabels).toEqual(['label1', 'label2', 'label3', 'label4', 'label5']);
});
it('Should build point labels using the user supplied callback', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [10, 5, 0, 25, 78]
}],
labels: ['label1', 'label2', 'label3', 'label4', 'label5']
},
options: {
scale: {
pointLabels: {
callback: function(value, index) {
return index.toString();
}
}
}
}
});
expect(chart.scale.pointLabels).toEqual(['0', '1', '2', '3', '4']);
});
it('should correctly set the center point', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [10, 5, 0, 25, 78]
}],
labels: ['label1', 'label2', 'label3', 'label4', 'label5']
},
options: {
scale: {
pointLabels: {
callback: function(value, index) {
return index.toString();
}
}
}
}
});
expect(chart.scale.drawingArea).toBe(233);
expect(chart.scale.xCenter).toBe(256);
expect(chart.scale.yCenter).toBe(280);
});
it('should correctly get the label for a given data index', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [10, 5, 0, 25, 78]
}],
labels: ['label1', 'label2', 'label3', 'label4', 'label5']
},
options: {
scale: {
pointLabels: {
callback: function(value, index) {
return index.toString();
}
}
}
}
});
expect(chart.scale.getLabelForIndex(1, 0)).toBe(5);
});
it('should get the correct distance from the center point', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [10, 5, 0, 25, 78]
}],
labels: ['label1', 'label2', 'label3', 'label4', 'label5']
},
options: {
scale: {
pointLabels: {
callback: function(value, index) {
return index.toString();
}
}
}
}
});
expect(chart.scale.getDistanceFromCenterForValue(chart.scale.min)).toBe(0);
expect(chart.scale.getDistanceFromCenterForValue(chart.scale.max)).toBe(233);
expect(chart.scale.getPointPositionForValue(1, 5)).toEqual({
x: 270,
y: 275,
});
chart.scale.options.ticks.reverse = true;
chart.update();
expect(chart.scale.getDistanceFromCenterForValue(chart.scale.min)).toBe(233);
expect(chart.scale.getDistanceFromCenterForValue(chart.scale.max)).toBe(0);
});
it('should correctly get angles for all points', function() {
var chart = window.acquireChart({
type: 'radar',
data: {
datasets: [{
data: [10, 5, 0, 25, 78]
}],
labels: ['label1', 'label2', 'label3', 'label4', 'label5']
},
options: {
scale: {
pointLabels: {
callback: function(value, index) {
return index.toString();
}
}
},
startAngle: 15
}
});
var radToNearestDegree = function(rad) {
return Math.round((360 * rad) / (2 * Math.PI));
};
var slice = 72; // (360 / 5)
for (var i = 0; i < 5; i++) {
expect(radToNearestDegree(chart.scale.getIndexAngle(i))).toBe(15 + (slice * i));
}
chart.options.startAngle = 0;
chart.update();
for (var x = 0; x < 5; x++) {
expect(radToNearestDegree(chart.scale.getIndexAngle(x))).toBe((slice * x));
}
});
});

File diff suppressed because it is too large Load Diff