d0f8b663a39068cca476b6f52deedb1775f3bc29
[WebKit-https.git] / Websites / perf.webkit.org / browser-tests / time-series-chart-tests.js
1 const scripts = [
2     '../shared/statistics.js',
3     'instrumentation.js',
4     'models/data-model.js',
5     'models/metric.js',
6     'models/time-series.js',
7     'models/measurement-set.js',
8     'models/measurement-cluster.js',
9     'models/measurement-adaptor.js',
10     'components/base.js',
11     'components/time-series-chart.js',
12     'components/interactive-time-series-chart.js'];
13
14 function posixTime(string) { return +new Date(string); }
15
16 const dayInMilliseconds = 24 * 3600 * 1000;
17
18 const sampleCluster = {
19     "clusterStart": posixTime('2016-01-01T00:00:00Z'),
20     "clusterSize": 7 * dayInMilliseconds,
21     "startTime": posixTime('2016-01-01T00:00:00Z'),
22     "endTime": posixTime('2016-01-08T00:00:00Z'),
23     "lastModified": posixTime('2016-01-18T00:00:00Z'),
24     "clusterCount": 1,
25     "status": "OK",
26     "formatMap": [
27         "id", "mean", "iterationCount", "sum", "squareSum", "markedOutlier",
28         "revisions",
29         "commitTime", "build", "buildTime", "buildNumber", "builder"
30     ],
31     "configurations": {
32         "current": [
33             [
34                 1000, 100, 1, 100, 100, false,
35                 [ [ 2000, 1, "4000", posixTime('2016-01-05T17:35:00Z')] ],
36                 posixTime('2016-01-05T17:35:00Z'), 5000, posixTime('2016-01-05T19:23:00Z'), "10", 7
37             ],
38             [
39                 1001, 131, 1, 131, 131, true,
40                 [ [ 2001, 1, "4001", posixTime('2016-01-05T18:43:01Z')] ],
41                 posixTime('2016-01-05T18:43:01Z'), 5001, posixTime('2016-01-05T20:58:01Z'), "11", 7
42             ],
43             [
44                 1002, 122, 1, 122, 122, false,
45                 [ [ 2002, 1, "4002", posixTime('2016-01-05T20:01:02Z') ] ],
46                 posixTime('2016-01-05T20:01:02Z'), 5002, posixTime('2016-01-05T22:37:02Z'), "12", 7
47             ],
48             [
49                 1003, 113, 1, 113, 113, false,
50                 [ [ 2003, 1, "4003", posixTime('2016-01-05T23:19:03Z') ] ],
51                 posixTime('2016-01-05T23:19:03Z'), 5003, posixTime('2016-01-06T23:19:03Z'), "13", 7
52             ],
53             [
54                 1004, 124, 1, 124, 124, false,
55                 [ [ 2004, 1, "4004", posixTime('2016-01-06T01:52:04Z') ] ],
56                 posixTime('2016-01-06T01:52:04Z'), 5004, posixTime('2016-01-06T02:42:04Z'), "14", 7
57             ],
58             [
59                 1005, 115, 1, 115, 115, true,
60                 [ [ 2005, 1, "4005", posixTime('2016-01-06T03:22:05Z') ] ],
61                 posixTime('2016-01-06T03:22:05Z'), 5005, posixTime('2016-01-06T06:01:05Z'), "15", 7
62             ],
63             [
64                 1006, 116, 1, 116, 116, false,
65                 [ [ 2006, 1, "4006", posixTime('2016-01-06T05:59:06Z') ] ],
66                 posixTime('2016-01-06T05:59:06Z'), 5006, posixTime('2016-01-06T08:34:06Z'), "16", 7
67             ]
68         ]
69     },
70 };
71
72 function createChartWithSampleCluster(context, chartOptions = {}, options = {})
73 {
74     const TimeSeriesChart = context.symbols[options.interactiveChart ? 'InteractiveTimeSeriesChart' : 'TimeSeriesChart'];
75     const MeasurementSet = context.symbols.MeasurementSet;
76
77     const chart = new TimeSeriesChart([
78         {
79             type: 'current',
80             measurementSet: MeasurementSet.findSet(1, 1, 0),
81             interactive: options.interactive || false,
82             includeOutliers: options.includeOutliers || false
83         }], chartOptions);
84     const element = chart.element();
85     element.style.width = options.width || '300px';
86     element.style.height = options.height || '100px';
87     context.document.body.appendChild(element);
88
89     return chart;
90 }
91
92 function respondWithSampleCluster(request)
93 {
94     expect(request.url).to.be('../data/measurement-set-1-1.json');
95     expect(request.method).to.be('GET');
96     request.resolve(sampleCluster);
97 }
98
99 describe('TimeSeriesChart', () => {
100
101     it('should be constructible with an empty sourec list and an empty options', () => {
102         return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
103             new TimeSeriesChart([], {});
104         });
105     });
106
107     describe('computeTimeGrid', () => {
108         it('should return an empty array when the start and the end times are identical', () => {
109             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
110                 const someTime = Date.now();
111                 const labels = TimeSeriesChart.computeTimeGrid(someTime, someTime, 0);
112                 expect(labels).to.be.a('array');
113                 expect(labels.length).to.be(0);
114             });
115         });
116
117         const millisecondsPerHour = 3600 * 1000;
118         const millisecondsPerDay = 24 * millisecondsPerHour;
119
120         it('should return an empty array when maxLabels is 0', () => {
121             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
122                 const endTime = Date.now();
123                 const labels = TimeSeriesChart.computeTimeGrid(endTime - millisecondsPerDay, endTime, 0);
124                 expect(labels).to.be.a('array');
125                 expect(labels.length).to.be(0);
126             });
127         });
128
129         it('should return an empty array when maxLabels is 0 even when the interval spans multiple months', () => {
130             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
131                 const endTime = Date.now();
132                 const labels = TimeSeriesChart.computeTimeGrid(endTime - 120 * millisecondsPerDay, endTime, 0);
133                 expect(labels).to.be.a('array');
134                 expect(labels.length).to.be(0);
135             });
136         });
137
138         function checkGridItem(item, label, expectedDate)
139         {
140             expect(item.label).to.be(label);
141             expect(item.time.__proto__.constructor.name).to.be('Date');
142             expect(+item.time).to.be(+new Date(expectedDate));
143         }
144
145         it('should generate one hour label with just day for two hour interval when maxLabels is 1', () => {
146             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
147                 const endTime = new Date('2017-01-15T07:53:00Z');
148                 const labels = TimeSeriesChart.computeTimeGrid(endTime - 2 * millisecondsPerHour, +endTime, 1);
149                 expect(labels).to.be.a('array');
150                 expect(labels.length).to.be(1);
151                 checkGridItem(labels[0], '6AM', '2017-01-15T06:00:00Z');
152             });
153         });
154
155         it('should generate two two-hour labels for four hour interval when maxLabels is 2', () => {
156             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
157                 const endTime = new Date('2017-01-15T07:53:00Z');
158                 const labels = TimeSeriesChart.computeTimeGrid(endTime - 4 * millisecondsPerHour, +endTime, 2);
159                 expect(labels).to.be.a('array');
160                 expect(labels.length).to.be(2);
161                 checkGridItem(labels[0], '4AM', '2017-01-15T04:00:00Z');
162                 checkGridItem(labels[1], '6AM', '2017-01-15T06:00:00Z');
163             });
164         });
165
166         it('should generate six two-hour labels for twelve hour interval when maxLabels is 6', () => {
167             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
168                 const endTime = new Date('2017-01-15T07:53:00Z');
169                 const labels = TimeSeriesChart.computeTimeGrid(+endTime, +endTime + 12 * millisecondsPerHour, 6);
170                 expect(labels).to.be.a('array');
171                 expect(labels.length).to.be(6);
172                 checkGridItem(labels[0], '8AM', '2017-01-15T08:00:00Z');
173                 checkGridItem(labels[1], '10AM', '2017-01-15T10:00:00Z');
174                 checkGridItem(labels[2], '12PM', '2017-01-15T12:00:00Z');
175                 checkGridItem(labels[3], '2PM', '2017-01-15T14:00:00Z');
176                 checkGridItem(labels[4], '4PM', '2017-01-15T16:00:00Z');
177                 checkGridItem(labels[5], '6PM', '2017-01-15T18:00:00Z');
178             });
179         });
180
181         it('should generate six two-hour labels with one date label for twelve hour interval that cross a day when maxLabels is 6', () => {
182             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
183                 const endTime = new Date('2017-01-15T16:12:00Z');
184                 const labels = TimeSeriesChart.computeTimeGrid(+endTime, +endTime + 12 * millisecondsPerHour, 6);
185                 expect(labels).to.be.a('array');
186                 expect(labels.length).to.be(6);
187                 checkGridItem(labels[0], '6PM', '2017-01-15T18:00:00Z');
188                 checkGridItem(labels[1], '8PM', '2017-01-15T20:00:00Z');
189                 checkGridItem(labels[2], '10PM', '2017-01-15T22:00:00Z');
190                 checkGridItem(labels[3], '1/16', '2017-01-16T00:00:00Z');
191                 checkGridItem(labels[4], '2AM', '2017-01-16T02:00:00Z');
192                 checkGridItem(labels[5], '4AM', '2017-01-16T04:00:00Z');
193             });
194         });
195
196         it('should generate three two-hour labels for six hour interval that cross a year when maxLabels is 5', () => {
197             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
198                 const endTime = new Date('2016-12-31T21:37:00Z');
199                 const labels = TimeSeriesChart.computeTimeGrid(+endTime, +endTime + 6 * millisecondsPerHour, 5);
200                 expect(labels).to.be.a('array');
201                 expect(labels.length).to.be(3);
202                 checkGridItem(labels[0], '10PM', '2016-12-31T22:00:00Z');
203                 checkGridItem(labels[1], '1/1', '2017-01-01T00:00:00Z');
204                 checkGridItem(labels[2], '2AM', '2017-01-01T02:00:00Z');
205             });
206         });
207
208         it('should generate one one-day label for one day interval when maxLabels is 1', () => {
209             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
210                 const endTime = new Date('2017-01-15T07:53:00Z');
211                 const labels = TimeSeriesChart.computeTimeGrid(endTime - millisecondsPerDay, +endTime, 1);
212                 expect(labels).to.be.a('array');
213                 expect(labels.length).to.be(1);
214                 checkGridItem(labels[0], '1/15', '2017-01-15T00:00:00Z');
215             });
216         });
217
218         it('should generate two one-day labels for one day interval when maxLabels is 2', () => {
219             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
220                 const endTime = new Date('2017-01-15T07:53:00Z');
221                 const labels = TimeSeriesChart.computeTimeGrid(endTime - millisecondsPerDay, +endTime, 2);
222                 expect(labels).to.be.a('array');
223                 expect(labels.length).to.be(2);
224                 checkGridItem(labels[0], '1/14 12PM', '2017-01-14T12:00:00Z');
225                 checkGridItem(labels[1], '1/15', '2017-01-15T00:00:00Z');
226             });
227         });
228
229         it('should generate four half-day labels for two day interval when maxLabels is 5', () => {
230             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
231                 const endTime = new Date('2017-01-15T16:12:00Z');
232                 const labels = TimeSeriesChart.computeTimeGrid(+endTime, +endTime + 2 * millisecondsPerDay, 5);
233                 expect(labels).to.be.a('array');
234                 expect(labels.length).to.be(4);
235                 checkGridItem(labels[0], '1/16', '2017-01-16T00:00:00Z');
236                 checkGridItem(labels[1], '12PM', '2017-01-16T12:00:00Z');
237                 checkGridItem(labels[2], '1/17', '2017-01-17T00:00:00Z');
238                 checkGridItem(labels[3], '12PM', '2017-01-17T12:00:00Z');
239             });
240         });
241
242         it('should generate four half-day labels for two day interval that cross a year when maxLabels is 5', () => {
243             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
244                 const endTime = new Date('2016-12-31T09:12:00Z');
245                 const labels = TimeSeriesChart.computeTimeGrid(+endTime, +endTime + 2 * millisecondsPerDay, 5);
246                 expect(labels).to.be.a('array');
247                 expect(labels.length).to.be(4);
248                 checkGridItem(labels[0], '12/31 12PM', '2016-12-31T12:00:00Z');
249                 checkGridItem(labels[1], '1/1', '2017-01-01T00:00:00Z');
250                 checkGridItem(labels[2], '12PM', '2017-01-01T12:00:00Z');
251                 checkGridItem(labels[3], '1/2', '2017-01-02T00:00:00Z');
252             });
253         });
254
255         it('should generate seven per-day labels for one week interval when maxLabels is 10', () => {
256             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
257                 const endTime = new Date('2017-01-15T07:53:00Z');
258                 const labels = TimeSeriesChart.computeTimeGrid(endTime - 7 * millisecondsPerDay, endTime, 10);
259                 expect(labels).to.be.a('array');
260                 expect(labels.length).to.be(7);
261                 checkGridItem(labels[0], '1/9', '2017-01-09T00:00:00Z');
262                 checkGridItem(labels[1], '1/10', '2017-01-10T00:00:00Z');
263                 checkGridItem(labels[2], '1/11', '2017-01-11T00:00:00Z');
264                 checkGridItem(labels[3], '1/12', '2017-01-12T00:00:00Z');
265                 checkGridItem(labels[4], '1/13', '2017-01-13T00:00:00Z');
266                 checkGridItem(labels[5], '1/14', '2017-01-14T00:00:00Z');
267                 checkGridItem(labels[6], '1/15', '2017-01-15T00:00:00Z');
268             });
269         });
270
271         it('should generate three two-day labels for one week interval when maxLabels is 4', () => {
272             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
273                 const endTime = new Date('2017-01-15T07:53:00Z');
274                 const labels = TimeSeriesChart.computeTimeGrid(endTime - 7 * millisecondsPerDay, endTime, 4);
275                 expect(labels).to.be.a('array');
276                 expect(labels.length).to.be(3);
277                 checkGridItem(labels[0], '1/10', '2017-01-10T00:00:00Z');
278                 checkGridItem(labels[1], '1/12', '2017-01-12T00:00:00Z');
279                 checkGridItem(labels[2], '1/14', '2017-01-14T00:00:00Z');
280             });
281         });
282
283         it('should generate seven one-day labels for two week interval when maxLabels is 8', () => {
284             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
285                 const endTime = new Date('2017-01-15T18:53:00Z');
286                 const labels = TimeSeriesChart.computeTimeGrid(+endTime, +endTime + 14 * millisecondsPerDay, 8);
287                 expect(labels).to.be.a('array');
288                 expect(labels.length).to.be(7);
289                 checkGridItem(labels[0], '1/17', '2017-01-17T00:00:00Z');
290                 checkGridItem(labels[1], '1/19', '2017-01-19T00:00:00Z');
291                 checkGridItem(labels[2], '1/21', '2017-01-21T00:00:00Z');
292                 checkGridItem(labels[3], '1/23', '2017-01-23T00:00:00Z');
293                 checkGridItem(labels[4], '1/25', '2017-01-25T00:00:00Z');
294                 checkGridItem(labels[5], '1/27', '2017-01-27T00:00:00Z');
295                 checkGridItem(labels[6], '1/29', '2017-01-29T00:00:00Z');
296             });
297         });
298
299         it('should generate two one-week labels for two week interval when maxLabels is 3', () => {
300             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
301                 const endTime = new Date('2017-01-15T18:53:00Z');
302                 const labels = TimeSeriesChart.computeTimeGrid(+endTime, +endTime + 14 * millisecondsPerDay, 3);
303                 expect(labels).to.be.a('array');
304                 expect(labels.length).to.be(2);
305                 checkGridItem(labels[0], '1/22', '2017-01-22T00:00:00Z');
306                 checkGridItem(labels[1], '1/29', '2017-01-29T00:00:00Z');
307             });
308         });
309
310         it('should generate seven one-month labels for six and half months interval starting before 15th when maxLabels is 7', () => {
311             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
312                 const endTime = new Date('2017-01-15T18:53:00Z');
313                 const labels = TimeSeriesChart.computeTimeGrid(new Date('2016-07-12T18:53:00Z'), new Date('2017-01-18T08:17:53Z'), 7);
314                 expect(labels).to.be.a('array');
315                 expect(labels.length).to.be(7);
316                 checkGridItem(labels[0], '7/15', '2016-07-15T00:00:00Z');
317                 checkGridItem(labels[1], '8/15', '2016-08-15T00:00:00Z');
318                 checkGridItem(labels[2], '9/15', '2016-09-15T00:00:00Z');
319                 checkGridItem(labels[3], '10/15', '2016-10-15T00:00:00Z');
320                 checkGridItem(labels[4], '11/15', '2016-11-15T00:00:00Z');
321                 checkGridItem(labels[5], '12/15', '2016-12-15T00:00:00Z');
322                 checkGridItem(labels[6], '1/15', '2017-01-15T00:00:00Z');
323             });
324         });
325
326         it('should generate seven one-month labels for six months interval staring after 15th when maxLabels is 7', () => {
327             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
328                 const endTime = new Date('2017-01-15T18:53:00Z');
329                 const labels = TimeSeriesChart.computeTimeGrid(new Date('2016-07-18T18:53:00Z'), new Date('2017-01-18T08:17:53Z'), 7);
330                 expect(labels).to.be.a('array');
331                 expect(labels.length).to.be(6);
332                 checkGridItem(labels[0], '8/1', '2016-08-01T00:00:00Z');
333                 checkGridItem(labels[1], '9/1', '2016-09-01T00:00:00Z');
334                 checkGridItem(labels[2], '10/1', '2016-10-01T00:00:00Z');
335                 checkGridItem(labels[3], '11/1', '2016-11-01T00:00:00Z');
336                 checkGridItem(labels[4], '12/1', '2016-12-01T00:00:00Z');
337             });
338         });
339
340         it('should generate six two-months labels for one year interval when maxLabels is 7', () => {
341             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
342                 const endTime = new Date('2017-01-15T18:53:00Z');
343                 const labels = TimeSeriesChart.computeTimeGrid(new Date('2016-07-11T18:53:00Z'), new Date('2017-07-27T08:17:53Z'), 7);
344                 expect(labels).to.be.a('array');
345                 expect(labels.length).to.be(6);
346                 checkGridItem(labels[0], '9/1', '2016-09-01T00:00:00Z');
347                 checkGridItem(labels[1], '11/1', '2016-11-01T00:00:00Z');
348                 checkGridItem(labels[2], '1/1', '2017-01-01T00:00:00Z');
349                 checkGridItem(labels[3], '3/1', '2017-03-01T00:00:00Z');
350                 checkGridItem(labels[4], '5/1', '2017-05-01T00:00:00Z');
351                 checkGridItem(labels[5], '7/1', '2017-07-01T00:00:00Z');
352             });
353         });
354
355         it('should generate four three-months labels for one year interval when maxLabels is 5', () => {
356             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart').then((TimeSeriesChart) => {
357                 const endTime = new Date('2017-01-15T18:53:00Z');
358                 const labels = TimeSeriesChart.computeTimeGrid(new Date('2016-07-11T18:53:00Z'), new Date('2017-07-27T08:17:53Z'), 5);
359                 expect(labels).to.be.a('array');
360                 expect(labels.length).to.be(4);
361                 checkGridItem(labels[0], '10/1', '2016-10-01T00:00:00Z');
362                 checkGridItem(labels[1], '1/1', '2017-01-01T00:00:00Z');
363                 checkGridItem(labels[2], '4/1', '2017-04-01T00:00:00Z');
364                 checkGridItem(labels[3], '7/1', '2017-07-01T00:00:00Z');
365             });
366         });
367     });
368
369     describe('computeValueGrid', () => {
370
371         function checkValueGrid(actual, expected) {
372             expect(actual).to.be.a('array');
373             expect(JSON.stringify(actual)).to.be(JSON.stringify(expected));
374         }
375
376         function approximate(number)
377         {
378             return Math.round(number * 100000000) / 100000000;
379         }
380
381         it('should generate [0.5, 1.0, 1.5, 2.0] for [0.3, 2.3] when maxLabels is 5', () => {
382             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart', 'Metric').then((symbols) => {
383                 const [TimeSeriesChart, Metric] = symbols;
384                 const grid = TimeSeriesChart.computeValueGrid(0.3, 2.3, 5, Metric.makeFormatter('pt', 2));
385                 expect(grid.map((item) => approximate(item.value))).to.eql([0.5, 1.0, 1.5, 2.0]);
386                 expect(grid.map((item) => item.label)).to.eql(['0.5 pt', '1.0 pt', '1.5 pt', '2.0 pt']);
387             });
388         });
389
390         it('should generate [0.4, 0.6, 0.8, 1, 1.2, 1.4, 1.6, 1.8, 2, 2.2] for [0.3, 2.3] when maxLabels is 10', () => {
391             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart', 'Metric').then((symbols) => {
392                 const [TimeSeriesChart, Metric] = symbols;
393                 const grid = TimeSeriesChart.computeValueGrid(0.3, 2.3, 10, Metric.makeFormatter('pt', 2));
394                 expect(grid.map((item) => approximate(item.value))).to.eql([0.4, 0.6, 0.8, 1, 1.2, 1.4, 1.6, 1.8, 2, 2.2]);
395                 expect(grid.map((item) => item.label)).to.eql(['0.4 pt', '0.6 pt', '0.8 pt', '1.0 pt', '1.2 pt', '1.4 pt', '1.6 pt', '1.8 pt', '2.0 pt', '2.2 pt']);
396             });
397         });
398
399         it('should generate [1, 2] for [0.3, 2.3] when maxLabels is 2', () => {
400             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart', 'Metric').then((symbols) => {
401                 const [TimeSeriesChart, Metric] = symbols;
402                 const grid = TimeSeriesChart.computeValueGrid(0.3, 2.3, 2, Metric.makeFormatter('pt', 2));
403                 expect(grid.map((item) => item.value)).to.eql([1, 2]);
404                 expect(grid.map((item) => item.label)).to.eql(['1.0 pt', '2.0 pt']);
405             });
406         });
407
408         it('should generate [0.4, 0.6, 0.8, 1.0, 1.2] for [0.3, 1.3] when maxLabels is 5', () => {
409             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart', 'Metric').then((symbols) => {
410                 const [TimeSeriesChart, Metric] = symbols;
411                 const grid = TimeSeriesChart.computeValueGrid(0.3, 1.3, 5, Metric.makeFormatter('pt', 2));
412                 expect(grid.map((item) => approximate(item.value))).to.eql([0.4, 0.6, 0.8, 1.0, 1.2]);
413                 expect(grid.map((item) => item.label)).to.eql(['0.4 pt', '0.6 pt', '0.8 pt', '1.0 pt', '1.2 pt']);
414             });
415         });
416
417         it('should generate [0.2, 0.4, 0.6, 0.8, 1, 1.2] for [0.2, 1.3] when maxLabels is 10', () => {
418             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart', 'Metric').then((symbols) => {
419                 const [TimeSeriesChart, Metric] = symbols;
420                 const grid = TimeSeriesChart.computeValueGrid(0.2, 1.3, 10, Metric.makeFormatter('pt', 2));
421                 expect(grid.map((item) => approximate(item.value))).to.eql([0.2, 0.4, 0.6, 0.8, 1, 1.2]);
422                 expect(grid.map((item) => item.label)).to.eql(['0.2 pt', '0.4 pt', '0.6 pt', '0.8 pt', '1.0 pt', '1.2 pt']);
423             });
424         });
425
426         it('should generate [0.5, 1.0] for [0.3, 1.3] when maxLabels is 4', () => {
427             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart', 'Metric').then((symbols) => {
428                 const [TimeSeriesChart, Metric] = symbols;
429                 const grid = TimeSeriesChart.computeValueGrid(0.3, 1.3, 4, Metric.makeFormatter('pt', 2));
430                 expect(grid.map((item) => approximate(item.value))).to.eql([0.5, 1.0]);
431                 expect(grid.map((item) => item.label)).to.eql(['0.5 pt', '1.0 pt']);
432             });
433         });
434
435         it('should generate [10, 20, 30] for [4, 35] when maxLabels is 4', () => {
436             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart', 'Metric').then((symbols) => {
437                 const [TimeSeriesChart, Metric] = symbols;
438                 const grid = TimeSeriesChart.computeValueGrid(4, 35, 4, Metric.makeFormatter('pt', 2));
439                 expect(grid.map((item) => item.value)).to.eql([10, 20, 30]);
440                 expect(grid.map((item) => item.label)).to.eql(['10 pt', '20 pt', '30 pt']);
441             });
442         });
443
444         it('should generate [10, 20, 30] for [4, 35] when maxLabels is 6', () => {
445             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart', 'Metric').then((symbols) => {
446                 const [TimeSeriesChart, Metric] = symbols;
447                 const grid = TimeSeriesChart.computeValueGrid(4, 35, 6, Metric.makeFormatter('pt', 2));
448                 expect(grid.map((item) => item.value)).to.eql([10, 20, 30]);
449                 expect(grid.map((item) => item.label)).to.eql(['10 pt', '20 pt', '30 pt']);
450             });
451         });
452
453         it('should generate [10, 15, 20, 25, 30, 35] for [6, 35] when maxLabels is 6', () => {
454             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart', 'Metric').then((symbols) => {
455                 const [TimeSeriesChart, Metric] = symbols;
456                 const grid = TimeSeriesChart.computeValueGrid(6, 35, 6, Metric.makeFormatter('pt', 2));
457                 expect(grid.map((item) => item.value)).to.eql([10, 15, 20, 25, 30, 35]);
458                 expect(grid.map((item) => item.label)).to.eql(['10 pt', '15 pt', '20 pt', '25 pt', '30 pt', '35 pt']);
459             });
460         });
461
462         it('should generate [110, 115, 120, 125, 130] for [107, 134] when maxLabels is 6', () => {
463             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart', 'Metric').then((symbols) => {
464                 const [TimeSeriesChart, Metric] = symbols;
465                 const grid = TimeSeriesChart.computeValueGrid(107, 134, 6, Metric.makeFormatter('pt', 3));
466                 expect(grid.map((item) => item.value)).to.eql([110, 115, 120, 125, 130]);
467                 expect(grid.map((item) => item.label)).to.eql(['110 pt', '115 pt', '120 pt', '125 pt', '130 pt']);
468             });
469         });
470
471         it('should generate [5e7, 10e7] for [1e7, 1e8] when maxLabels is 4', () => {
472             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart', 'Metric').then((symbols) => {
473                 const [TimeSeriesChart, Metric] = symbols;
474                 const grid = TimeSeriesChart.computeValueGrid(1e7, 1e8, 4, Metric.makeFormatter('pt', 3));
475                 expect(grid.map((item) => item.value)).to.eql([5e7, 10e7]);
476                 expect(grid.map((item) => item.label)).to.eql(['50.0 Mpt', '100 Mpt']);
477             });
478         });
479
480         it('should generate [2e7, 4e7, 6e7, 8e7, 10e7] for [1e7, 1e8] when maxLabels is 5', () => {
481             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart', 'Metric').then((symbols) => {
482                 const [TimeSeriesChart, Metric] = symbols;
483                 const grid = TimeSeriesChart.computeValueGrid(1e7, 1e8, 5, Metric.makeFormatter('pt', 3));
484                 expect(grid.map((item) => item.value)).to.eql([2e7, 4e7, 6e7, 8e7, 10e7]);
485                 expect(grid.map((item) => item.label)).to.eql(['20.0 Mpt', '40.0 Mpt', '60.0 Mpt', '80.0 Mpt', '100 Mpt']);
486             });
487         });
488
489         it('should generate [-1.5, -1.0, -0.5, 0.0, 0.5] for [-1.8, 0.7] when maxLabels is 5', () => {
490             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart', 'Metric').then((symbols) => {
491                 const [TimeSeriesChart, Metric] = symbols;
492                 const grid = TimeSeriesChart.computeValueGrid(-1.8, 0.7, 5, Metric.makeFormatter('pt', 2));
493                 expect(grid.map((item) => approximate(item.value))).to.eql([-1.5, -1.0, -0.5, 0.0, 0.5]);
494                 expect(grid.map((item) => item.label)).to.eql(['-1.5 pt', '-1.0 pt', '-0.5 pt', '0.0 pt', '0.5 pt']);
495             });
496         });
497
498         it('should generate [200ms, 400ms, 600ms, 800ms, 1.00s, 1.20s] for [0.2, 1.3] when maxLabels is 10 and unit is seconds', () => {
499             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart', 'Metric').then((symbols) => {
500                 const [TimeSeriesChart, Metric] = symbols;
501                 const grid = TimeSeriesChart.computeValueGrid(0.2, 1.3, 10, Metric.makeFormatter('s', 3));
502                 expect(grid.map((item) => approximate(item.value))).to.eql([0.2, 0.4, 0.6, 0.8, 1, 1.2]);
503                 expect(grid.map((item) => item.label)).to.eql(['200 ms', '400 ms', '600 ms', '800 ms', '1.00 s', '1.20 s']);
504             });
505         });
506
507         it('should generate [2.0GB, 4.0GB, 6.0GB] for [1.2GB, 7.2GB] when maxLabels is 4 and unit is bytes', () => {
508             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart', 'Metric').then((symbols) => {
509                 const [TimeSeriesChart, Metric] = symbols;
510                 const gigabytes = Math.pow(1024, 3);
511                 const grid = TimeSeriesChart.computeValueGrid(1.2 * gigabytes, 7.2 * gigabytes, 4, Metric.makeFormatter('B', 2));
512                 expect(grid.map((item) => approximate(item.value))).to.eql([2 * gigabytes, 4 * gigabytes, 6 * gigabytes]);
513                 expect(grid.map((item) => item.label)).to.eql(['2.0 GB', '4.0 GB', '6.0 GB']);
514             });
515         });
516
517         it('should generate [0.6GB, 0.8GB, 1.0GB, 1.2GB] for [0.53GB, 1.23GB] when maxLabels is 4 and unit is bytes', () => {
518             return new BrowsingContext().importScripts(scripts, 'TimeSeriesChart', 'Metric').then((symbols) => {
519                 const [TimeSeriesChart, Metric] = symbols;
520                 const gigabytes = Math.pow(1024, 3);
521                 const grid = TimeSeriesChart.computeValueGrid(0.53 * gigabytes, 1.23 * gigabytes, 4, Metric.makeFormatter('B', 2));
522                 expect(grid.map((item) => item.label)).to.eql(['0.6 GB', '0.8 GB', '1.0 GB', '1.2 GB']);
523             });
524         });
525
526     });
527
528     describe('fetchMeasurementSets', () => {
529
530         it('should fetch the measurement set and create a canvas element upon receiving the data', () => {
531             const context = new BrowsingContext();
532             return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
533                 const chart = createChartWithSampleCluster(context);
534
535                 chart.setDomain(sampleCluster.startTime, sampleCluster.endTime);
536                 chart.fetchMeasurementSets();
537
538                 const requests = context.symbols.MockRemoteAPI.requests;
539                 expect(requests.length).to.be(1);
540                 respondWithSampleCluster(requests[0]);
541
542                 expect(chart.content().querySelector('canvas')).to.be(null);
543                 return waitForComponentsToRender(context).then(() => {
544                     console.log('done')
545                     expect(chart.content().querySelector('canvas')).to.not.be(null);
546                 });
547             });
548         });
549
550         it('should immediately enqueue to render when the measurement set had already been fetched', () => {
551             const context = new BrowsingContext();
552             return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
553                 const chart = createChartWithSampleCluster(context);
554
555                 let set = context.symbols.MeasurementSet.findSet(1, 1, 0);
556                 let promise = set.fetchBetween(sampleCluster.startTime, sampleCluster.endTime);
557
558                 const requests = context.symbols.MockRemoteAPI.requests;
559                 expect(requests.length).to.be(1);
560                 respondWithSampleCluster(requests[0]);
561
562                 return promise.then(() => {
563                     expect(chart.content().querySelector('canvas')).to.be(null);
564                     chart.setDomain(sampleCluster.startTime, sampleCluster.endTime);
565                     chart.fetchMeasurementSets();
566                     return waitForComponentsToRender(context);
567                 }).then(() => {
568                     expect(requests.length).to.be(1);
569                     expect(chart.content().querySelector('canvas')).to.not.be(null);
570                 });
571             });
572         });
573
574         it('should dispatch "dataChange" action once the fetched data becomes available', () => {
575             const context = new BrowsingContext();
576             return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
577                 const chart = createChartWithSampleCluster(context);
578
579                 let dataChangeCount = 0;
580                 chart.listenToAction('dataChange', () => dataChangeCount++);
581
582                 chart.setDomain(sampleCluster.startTime, sampleCluster.endTime);
583                 chart.fetchMeasurementSets();
584
585                 const requests = context.symbols.MockRemoteAPI.requests;
586                 expect(requests.length).to.be(1);
587                 respondWithSampleCluster(requests[0]);
588
589                 expect(dataChangeCount).to.be(0);
590                 expect(chart.sampledTimeSeriesData('current')).to.be(null);
591                 expect(chart.content().querySelector('canvas')).to.be(null);
592                 return waitForComponentsToRender(context).then(() => {
593                     expect(dataChangeCount).to.be(1);
594                     expect(chart.sampledTimeSeriesData('current')).to.not.be(null);
595                     expect(chart.content().querySelector('canvas')).to.not.be(null);
596                 });
597             });
598         });
599     });
600
601     describe('sampledTimeSeriesData', () => {
602         it('should not contain an outlier when includeOutliers is false', () => {
603             const context = new BrowsingContext();
604             return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
605                 const chart = createChartWithSampleCluster(context, {}, {includeOutliers: false});
606
607                 chart.setDomain(sampleCluster.startTime, sampleCluster.endTime);
608                 chart.fetchMeasurementSets();
609                 respondWithSampleCluster(context.symbols.MockRemoteAPI.requests[0]);
610
611                 return waitForComponentsToRender(context).then(() => {
612                     const view = chart.sampledTimeSeriesData('current');
613                     expect(view.length()).to.be(5);
614                     for (let point of view)
615                         expect(point.markedOutlier).to.be(false);
616                 });
617             });
618         });
619
620         it('should contain every outlier when includeOutliers is true', () => {
621             const context = new BrowsingContext();
622             return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
623                 const chart = createChartWithSampleCluster(context, {}, {includeOutliers: true});
624
625                 chart.setDomain(sampleCluster.startTime, sampleCluster.endTime);
626                 chart.fetchMeasurementSets();
627                 respondWithSampleCluster(context.symbols.MockRemoteAPI.requests[0]);
628
629                 return waitForComponentsToRender(context).then(() => {
630                     const view = chart.sampledTimeSeriesData('current');
631                     expect(view.length()).to.be(7);
632                     expect(view.findPointByIndex(1).markedOutlier).to.be(true);
633                     expect(view.findPointByIndex(5).markedOutlier).to.be(true);
634                 });
635             });
636         });
637
638         it('should only contain data points in the domain and one preceding point when there are no succeeding points', () => {
639             const context = new BrowsingContext();
640             return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
641                 const chart = createChartWithSampleCluster(context, {}, {includeOutliers: true});
642
643                 chart.setDomain(posixTime('2016-01-06T00:00:00Z'), posixTime('2016-01-07T00:00:00Z'));
644                 chart.fetchMeasurementSets();
645                 respondWithSampleCluster(context.symbols.MockRemoteAPI.requests[0]);
646
647                 return waitForComponentsToRender(context).then(() => {
648                     const view = chart.sampledTimeSeriesData('current');
649                     expect([...view].map((point) => point.id)).to.be.eql([1003, 1004, 1005, 1006]);
650                 });
651             });
652         });
653
654         it('should only contain data points in the domain and one succeeding point when there are no preceding points', () => {
655             const context = new BrowsingContext();
656             return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
657                 const chart = createChartWithSampleCluster(context, {}, {includeOutliers: true});
658
659                 chart.setDomain(posixTime('2016-01-05T00:00:00Z'), posixTime('2016-01-06T00:00:00Z'));
660                 chart.fetchMeasurementSets();
661                 chart.fetchMeasurementSets();
662                 respondWithSampleCluster(context.symbols.MockRemoteAPI.requests[0]);
663
664                 return waitForComponentsToRender(context).then(() => {
665                     const view = chart.sampledTimeSeriesData('current');
666                     expect([...view].map((point) => point.id)).to.be.eql([1000, 1001, 1002, 1003, 1004]);
667                 });
668             });
669         });
670
671         it('should only contain data points in the domain and one preceding point and one succeeding point', () => {
672             const context = new BrowsingContext();
673             return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
674                 const chart = createChartWithSampleCluster(context, {}, {includeOutliers: true});
675
676                 chart.setDomain(posixTime('2016-01-05T21:00:00Z'), posixTime('2016-01-06T02:00:00Z'));
677                 chart.fetchMeasurementSets();
678                 respondWithSampleCluster(context.symbols.MockRemoteAPI.requests[0]);
679
680                 return waitForComponentsToRender(context).then(() => {
681                     const view = chart.sampledTimeSeriesData('current');
682                     expect([...view].map((point) => point.id)).to.be.eql([1002, 1003, 1004, 1005]);
683                 });
684             });
685         });
686     });
687
688     describe('render', () => {
689         it('should update the canvas size and its content after the window has been resized', () => {
690             const context = new BrowsingContext();
691             return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
692                 const chart = createChartWithSampleCluster(context, {}, {width: '100%', height: '100%'});
693
694                 let dataChangeCount = 0;
695                 chart.listenToAction('dataChange', () => dataChangeCount++);
696
697                 chart.setDomain(sampleCluster.startTime, sampleCluster.endTime);
698                 chart.fetchMeasurementSets();
699
700                 const requests = context.symbols.MockRemoteAPI.requests;
701                 expect(requests.length).to.be(1);
702                 respondWithSampleCluster(requests[0]);
703
704                 expect(dataChangeCount).to.be(0);
705                 expect(chart.sampledTimeSeriesData('current')).to.be(null);
706                 expect(chart.content().querySelector('canvas')).to.be(null);
707                 let canvas;
708                 let originalWidth;
709                 let originalHeight;
710                 return waitForComponentsToRender(context).then(() => {
711                     expect(dataChangeCount).to.be(1);
712                     expect(chart.sampledTimeSeriesData('current')).to.not.be(null);
713                     canvas = chart.content().querySelector('canvas');
714                     expect(canvas).to.not.be(null);
715
716                     originalWidth = canvas.offsetWidth;
717                     originalHeight = canvas.offsetHeight;
718                     expect(originalWidth).to.be(context.document.body.offsetWidth);
719                     expect(originalHeight).to.be(context.document.body.offsetHeight);
720
721                     CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
722                     context.iframe.style.width = context.iframe.offsetWidth * 2 + 'px';
723                     context.global.dispatchEvent(new Event('resize'));
724
725                     expect(canvas.offsetWidth).to.be(originalWidth);
726                     expect(canvas.offsetHeight).to.be(originalHeight);
727
728                     return waitForComponentsToRender(context);
729                 }).then(() => {
730                     expect(dataChangeCount).to.be(2);
731                     expect(canvas.offsetWidth).to.be.greaterThan(originalWidth);
732                     expect(canvas.offsetWidth).to.be(context.document.body.offsetWidth);
733                     expect(canvas.offsetHeight).to.be(originalHeight);
734                     expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(true);
735                 });
736             });
737         });
738
739         it('should not update update the canvas when the window has been resized but its dimensions stays the same', () => {
740             const context = new BrowsingContext();
741             return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
742                 const chart = createChartWithSampleCluster(context, {}, {width: '100px', height: '100px'});
743
744                 let dataChangeCount = 0;
745                 chart.listenToAction('dataChange', () => dataChangeCount++);
746
747                 chart.setDomain(sampleCluster.startTime, sampleCluster.endTime);
748                 chart.fetchMeasurementSets();
749
750                 const requests = context.symbols.MockRemoteAPI.requests;
751                 expect(requests.length).to.be(1);
752                 respondWithSampleCluster(requests[0]);
753                 expect(dataChangeCount).to.be(0);
754
755                 let canvas;
756                 let data;
757                 return waitForComponentsToRender(context).then(() => {
758                     expect(dataChangeCount).to.be(1);
759                     data = chart.sampledTimeSeriesData('current');
760                     expect(data).to.not.be(null);
761                     canvas = chart.content().querySelector('canvas');
762                     expect(canvas).to.not.be(null);
763
764                     expect(canvas.offsetWidth).to.be(100);
765                     expect(canvas.offsetHeight).to.be(100);
766
767                     CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
768                     context.iframe.style.width = context.iframe.offsetWidth * 2 + 'px';
769                     context.global.dispatchEvent(new Event('resize'));
770
771                     expect(canvas.offsetWidth).to.be(100);
772                     expect(canvas.offsetHeight).to.be(100);
773
774                     return waitForComponentsToRender(context);
775                 }).then(() => {
776                     expect(dataChangeCount).to.be(1);
777                     expect(chart.sampledTimeSeriesData('current')).to.be(data);
778                     expect(canvas.offsetWidth).to.be(100);
779                     expect(canvas.offsetHeight).to.be(100);
780                     expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(false);
781                 });
782             });
783         });
784
785         it('should render Y-axis', () => {
786             const context = new BrowsingContext();
787             return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI', 'Metric').then(() => {
788                 const chartWithoutYAxis = createChartWithSampleCluster(context, {axis:
789                     {
790                         gridStyle: '#ccc',
791                         fontSize: 1,
792                         valueFormatter: context.symbols.Metric.makeFormatter('ms', 3),
793                     }
794                 });
795                 const chartWithYAxis1 = createChartWithSampleCluster(context, {axis:
796                     {
797                         yAxisWidth: 4,
798                         gridStyle: '#ccc',
799                         fontSize: 1,
800                         valueFormatter: context.symbols.Metric.makeFormatter('ms', 3),
801                     }
802                 });
803                 const chartWithYAxis2 = createChartWithSampleCluster(context, {axis:
804                     {
805                         yAxisWidth: 4,
806                         gridStyle: '#ccc',
807                         fontSize: 1,
808                         valueFormatter: context.symbols.Metric.makeFormatter('B', 3),
809                     }
810                 });
811
812                 chartWithoutYAxis.setDomain(sampleCluster.startTime, sampleCluster.endTime);
813                 chartWithoutYAxis.fetchMeasurementSets();
814                 chartWithYAxis1.setDomain(sampleCluster.startTime, sampleCluster.endTime);
815                 chartWithYAxis1.fetchMeasurementSets();
816                 chartWithYAxis2.setDomain(sampleCluster.startTime, sampleCluster.endTime);
817                 chartWithYAxis2.fetchMeasurementSets();
818
819                 const requests = context.symbols.MockRemoteAPI.requests;
820                 expect(requests.length).to.be(1);
821                 respondWithSampleCluster(requests[0]);
822
823                 return waitForComponentsToRender(context).then(() => {
824                     let canvasWithoutYAxis = chartWithoutYAxis.content().querySelector('canvas');
825                     let canvasWithYAxis1 = chartWithYAxis1.content().querySelector('canvas');
826                     let canvasWithYAxis2 = chartWithYAxis2.content().querySelector('canvas');
827                     CanvasTest.expectCanvasesMismatch(canvasWithoutYAxis, canvasWithYAxis1);
828                     CanvasTest.expectCanvasesMismatch(canvasWithoutYAxis, canvasWithYAxis1);
829                     CanvasTest.expectCanvasesMismatch(canvasWithYAxis1, canvasWithYAxis2);
830
831                     let content1 = CanvasTest.canvasImageData(canvasWithYAxis1);
832                     let foundGridLine = false;
833                     for (let y = 0; y < content1.height; y++) {
834                         let endOfY = content1.width * 4 * y;
835                         let r = content1.data[endOfY - 4];
836                         let g = content1.data[endOfY - 3];
837                         let b = content1.data[endOfY - 2];
838                         if (r == 204 && g == 204 && b == 204) {
839                             foundGridLine = true;
840                             break;
841                         }
842                     }
843                     expect(foundGridLine).to.be(true);
844                 });
845             });
846         });
847
848         it('should render annotations', () => {
849             const context = new BrowsingContext();
850             return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'InteractiveTimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
851                 const options = {annotations: {
852                     textStyle: '#000',
853                     textBackground: '#fff',
854                     minWidth: 3,
855                     barHeight: 7,
856                     barSpacing: 2}};
857                 const chartWithoutAnnotations = createChartWithSampleCluster(context, options);
858                 const chartWithAnnotations = createChartWithSampleCluster(context, options);
859
860                 chartWithoutAnnotations.setDomain(sampleCluster.startTime, sampleCluster.endTime);
861                 chartWithoutAnnotations.fetchMeasurementSets();
862                 respondWithSampleCluster(context.symbols.MockRemoteAPI.requests[0]);
863
864                 chartWithAnnotations.setDomain(sampleCluster.startTime, sampleCluster.endTime);
865                 chartWithAnnotations.fetchMeasurementSets();
866
867                 let canvasWithAnnotations;
868                 return waitForComponentsToRender(context).then(() => {
869                     const diff = sampleCluster.endTime - sampleCluster.startTime;
870                     chartWithAnnotations.setAnnotations([{
871                         startTime: sampleCluster.startTime + diff / 4,
872                         endTime: sampleCluster.startTime + diff / 2,
873                         label: 'hello, world',
874                         fillStyle: 'rgb(0, 0, 255)',
875                     }]);
876
877                     canvasWithAnnotations = chartWithAnnotations.content().querySelector('canvas');
878                     CanvasTest.fillCanvasBeforeRedrawCheck(canvasWithAnnotations);
879                     return waitForComponentsToRender(context);
880                 }).then(() => {
881                     expect(CanvasTest.hasCanvasBeenRedrawn(canvasWithAnnotations)).to.be(true);
882                 
883                     const canvasWithoutAnnotations = chartWithoutAnnotations.content().querySelector('canvas');
884                     CanvasTest.expectCanvasesMismatch(canvasWithAnnotations, canvasWithoutAnnotations);
885                 });
886             });
887         });
888     });
889
890 });
891
892 describe('InteractiveTimeSeriesChart', () => {
893
894     it('should change the indicator to the point closest to the last mouse move position', () => {
895         const context = new BrowsingContext();
896         return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'InteractiveTimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
897             const chart = createChartWithSampleCluster(context, {}, {interactiveChart: true, interactive: true});
898
899             chart.setDomain(sampleCluster.startTime, sampleCluster.endTime);
900             chart.fetchMeasurementSets();
901             respondWithSampleCluster(context.symbols.MockRemoteAPI.requests[0]);
902
903             const indicatorChangeCalls = [];
904             chart.listenToAction('indicatorChange', (...args) => indicatorChangeCalls.push(args));
905
906             let selectionChangeCount = 0;
907             chart.listenToAction('selectionChange', () => selectionChangeCount++);
908
909             let canvas;
910             return waitForComponentsToRender(context).then(() => {
911                 expect(chart.currentSelection()).to.be(null);
912                 expect(chart.currentPoint()).to.be(null);
913                 expect(chart.lockedIndicator()).to.be(null);
914                 expect(indicatorChangeCalls).to.be.eql([]);
915
916                 canvas = chart.content().querySelector('canvas');
917                 const rect = canvas.getBoundingClientRect();
918                 canvas.dispatchEvent(new MouseEvent('mousemove', {target: canvas, clientX: rect.right - 1, clientY: rect.top + rect.height / 2, composed: true, bubbles: true}));
919
920                 CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
921                 return waitForComponentsToRender(context);
922             }).then(() => {
923                 expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(true);
924
925                 expect(chart.currentSelection()).to.be(null);
926                 expect(chart.currentPoint()).to.not.be(null);
927                 expect(chart.lockedIndicator()).to.be(null);
928                 const lastPoint = chart.sampledTimeSeriesData('current').lastPoint();
929                 expect(chart.currentPoint()).to.be(lastPoint);
930                 expect(indicatorChangeCalls).to.be.eql([[lastPoint.id, false]]);
931
932                 expect(selectionChangeCount).to.be(0);
933             });
934         });
935     });
936
937     it('should lock the indicator to the point closest to the clicked position', () => {
938         const context = new BrowsingContext();
939         return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'InteractiveTimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
940             const chart = createChartWithSampleCluster(context, {}, {interactiveChart: true, interactive: true});
941
942             chart.setDomain(sampleCluster.startTime, sampleCluster.endTime);
943             chart.fetchMeasurementSets();
944             respondWithSampleCluster(context.symbols.MockRemoteAPI.requests[0]);
945
946             const indicatorChangeCalls = [];
947             chart.listenToAction('indicatorChange', (...args) => indicatorChangeCalls.push(args));
948
949             let selectionChangeCount = 0;
950             chart.listenToAction('selectionChange', () => selectionChangeCount++);
951
952             let canvas;
953             return waitForComponentsToRender(context).then(() => {
954                 expect(chart.currentSelection()).to.be(null);
955                 expect(chart.currentPoint()).to.be(null);
956                 expect(chart.lockedIndicator()).to.be(null);
957                 expect(indicatorChangeCalls).to.be.eql([]);
958                 canvas = chart.content().querySelector('canvas');
959                 const rect = canvas.getBoundingClientRect();
960
961                 const x = rect.right - 1;
962                 const y = rect.top + rect.height / 2;
963                 canvas.dispatchEvent(new MouseEvent('mousemove', {target: canvas, clientX: x, clientY: y, composed: true, bubbles: true}));
964                 canvas.dispatchEvent(new MouseEvent('mousedown', {target: canvas, clientX: x, clientY: y, composed: true, bubbles: true}));
965                 canvas.dispatchEvent(new MouseEvent('mousemove', {target: canvas, clientX: x - 0.5, clientY: y + 0.5, composed: true, bubbles: true}));
966                 canvas.dispatchEvent(new MouseEvent('mouseup', {target: canvas, clientX: x - 0.5, clientY: y + 0.5, composed: true, bubbles: true}));
967                 canvas.dispatchEvent(new MouseEvent('click', {target: canvas, clientX: x - 0.5, clientY: y + 0.5, composed: true, bubbles: true}));
968
969                 CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
970                 return waitForComponentsToRender(context);
971             }).then(() => {
972                 expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(true);
973
974                 const lastPoint = chart.sampledTimeSeriesData('current').lastPoint();
975                 expect(chart.currentSelection()).to.be(null);
976                 expect(chart.currentPoint()).to.be(lastPoint);
977                 expect(chart.lockedIndicator()).to.be(lastPoint);
978                 expect(indicatorChangeCalls).to.be.eql([[lastPoint.id, false], [lastPoint.id, true]]);
979
980                 expect(selectionChangeCount).to.be(0);
981             });
982         });
983     });
984
985     it('should clear the unlocked indicator when the mouse cursor exits the chart', () => {
986         const context = new BrowsingContext();
987         return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'InteractiveTimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
988             const chart = createChartWithSampleCluster(context, {}, {interactiveChart: true, interactive: true});
989
990             chart.setDomain(sampleCluster.startTime, sampleCluster.endTime);
991             chart.fetchMeasurementSets();
992             respondWithSampleCluster(context.symbols.MockRemoteAPI.requests[0]);
993
994             const indicatorChangeCalls = [];
995             chart.listenToAction('indicatorChange', (...args) => indicatorChangeCalls.push(args));
996
997             let selectionChangeCount = 0;
998             chart.listenToAction('selectionChange', () => selectionChangeCount++);
999
1000             let canvas;
1001             let rect;
1002             let lastPoint;
1003             return waitForComponentsToRender(context).then(() => {
1004                 expect(chart.currentSelection()).to.be(null);
1005                 expect(chart.currentPoint()).to.be(null);
1006                 expect(chart.lockedIndicator()).to.be(null);
1007                 expect(indicatorChangeCalls).to.be.eql([]);
1008
1009                 canvas = chart.content().querySelector('canvas');
1010                 rect = canvas.getBoundingClientRect();
1011                 canvas.dispatchEvent(new MouseEvent('mousemove', {target: canvas, clientX: rect.right - 1, clientY: rect.top + rect.height / 2, composed: true, bubbles: true}));
1012
1013                 CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
1014                 return waitForComponentsToRender(context);
1015             }).then(() => {
1016                 expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(true);
1017
1018                 lastPoint = chart.sampledTimeSeriesData('current').lastPoint();
1019                 expect(chart.currentSelection()).to.be(null);
1020                 expect(chart.currentPoint()).to.be(lastPoint);
1021                 expect(chart.lockedIndicator()).to.be(null);
1022                 expect(indicatorChangeCalls).to.be.eql([[lastPoint.id, false]]);
1023
1024                 canvas.parentNode.dispatchEvent(new MouseEvent('mousemove', {target: canvas, clientX: rect.right + 50, clientY: rect.bottom + 50, composed: true, bubbles: true}));
1025                 canvas.dispatchEvent(new MouseEvent('mouseleave', {target: canvas, clientX: rect.right + 50, clientY: rect.bottom + 50, composed: true, bubbles: true}));
1026
1027                 CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
1028                 return waitForComponentsToRender(context);
1029             }).then(() => {
1030                 expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(true);
1031
1032                 expect(chart.currentSelection()).to.be(null);
1033                 expect(chart.currentPoint()).to.be(null);
1034                 expect(chart.lockedIndicator()).to.be(null);
1035                 expect(indicatorChangeCalls).to.be.eql([[lastPoint.id, false], [null, false]]);
1036
1037                 expect(selectionChangeCount).to.be(0);
1038             });
1039         });
1040     });
1041
1042     it('should not clear the locked indicator when the mouse cursor exits the chart', () => {
1043         const context = new BrowsingContext();
1044         return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'InteractiveTimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
1045             const chart = createChartWithSampleCluster(context, {}, {interactiveChart: true, interactive: true});
1046
1047             chart.setDomain(sampleCluster.startTime, sampleCluster.endTime);
1048             chart.fetchMeasurementSets();
1049             respondWithSampleCluster(context.symbols.MockRemoteAPI.requests[0]);
1050
1051             const indicatorChangeCalls = [];
1052             chart.listenToAction('indicatorChange', (...args) => indicatorChangeCalls.push(args));
1053
1054             let selectionChangeCount = 0;
1055             chart.listenToAction('selectionChange', () => selectionChangeCount++);
1056
1057             let canvas;
1058             let rect;
1059             let lastPoint;
1060             return waitForComponentsToRender(context).then(() => {
1061                 expect(chart.currentSelection()).to.be(null);
1062                 expect(chart.currentPoint()).to.be(null);
1063                 expect(chart.lockedIndicator()).to.be(null);
1064                 expect(indicatorChangeCalls).to.be.eql([]);
1065
1066                 canvas = chart.content().querySelector('canvas');
1067                 rect = canvas.getBoundingClientRect();
1068                 canvas.dispatchEvent(new MouseEvent('click', {target: canvas, clientX: rect.right - 1, clientY: rect.top + rect.height / 2, composed: true, bubbles: true}));
1069
1070                 CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
1071                 return waitForComponentsToRender(context);
1072             }).then(() => {
1073                 expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(true);
1074
1075                 lastPoint = chart.sampledTimeSeriesData('current').lastPoint();
1076                 expect(chart.currentSelection()).to.be(null);
1077                 expect(chart.currentPoint()).to.be(lastPoint);
1078                 expect(chart.lockedIndicator()).to.be(lastPoint);
1079                 expect(indicatorChangeCalls).to.be.eql([[lastPoint.id, true]]);
1080
1081                 canvas.parentNode.dispatchEvent(new MouseEvent('mousemove', {target: canvas, clientX: rect.right + 50, clientY: rect.bottom + 50, composed: true, bubbles: true}));
1082                 canvas.dispatchEvent(new MouseEvent('mouseleave', {target: canvas, clientX: rect.right + 50, clientY: rect.bottom + 50, composed: true, bubbles: true}));
1083
1084                 CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
1085                 return waitForComponentsToRender(context);
1086             }).then(() => {
1087                 expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(false);
1088
1089                 expect(chart.currentSelection()).to.be(null);
1090                 expect(chart.currentPoint()).to.be(lastPoint);
1091                 expect(chart.lockedIndicator()).to.be(lastPoint);
1092                 expect(indicatorChangeCalls).to.be.eql([[lastPoint.id, true]]);
1093
1094                 expect(selectionChangeCount).to.be(0);
1095             })
1096         });
1097     });
1098
1099     it('should clear the locked indicator when clicked', () => {
1100         const context = new BrowsingContext();
1101         return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'InteractiveTimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
1102             const chart = createChartWithSampleCluster(context, {}, {interactiveChart: true, interactive: true});
1103
1104             chart.setDomain(sampleCluster.startTime, sampleCluster.endTime);
1105             chart.fetchMeasurementSets();
1106             respondWithSampleCluster(context.symbols.MockRemoteAPI.requests[0]);
1107
1108             const indicatorChangeCalls = [];
1109             chart.listenToAction('indicatorChange', (...args) => indicatorChangeCalls.push(args));
1110
1111             let selectionChangeCount = 0;
1112             chart.listenToAction('selectionChange', () => selectionChangeCount++);
1113
1114             let canvas;
1115             let rect;
1116             let y;
1117             let lastPoint;
1118             return waitForComponentsToRender(context).then(() => {
1119                 expect(chart.currentSelection()).to.be(null);
1120                 expect(chart.currentPoint()).to.be(null);
1121                 expect(chart.lockedIndicator()).to.be(null);
1122                 expect(indicatorChangeCalls).to.be.eql([]);
1123
1124                 canvas = chart.content().querySelector('canvas');
1125                 rect = canvas.getBoundingClientRect();
1126                 y = rect.top + rect.height / 2;
1127                 canvas.dispatchEvent(new MouseEvent('click', {target: canvas, clientX: rect.right - 1, clientY: y, composed: true, bubbles: true}));
1128
1129                 CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
1130                 return waitForComponentsToRender(context);
1131             }).then(() => {
1132                 expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(true);
1133
1134                 lastPoint = chart.sampledTimeSeriesData('current').lastPoint();
1135                 expect(chart.currentSelection()).to.be(null);
1136                 expect(chart.currentPoint()).to.be(lastPoint);
1137                 expect(chart.lockedIndicator()).to.be(lastPoint);
1138                 expect(indicatorChangeCalls).to.be.eql([[lastPoint.id, true]]);
1139
1140                 canvas.dispatchEvent(new MouseEvent('click', {target: canvas, clientX: rect.left + 1, clientY: y, composed: true, bubbles: true}));
1141
1142                 CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
1143                 return waitForComponentsToRender(context);
1144             }).then(() => {
1145                 expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(true);
1146
1147                 expect(chart.currentSelection()).to.be(null);
1148                 const firstPoint = chart.sampledTimeSeriesData('current').firstPoint();
1149                 expect(chart.currentPoint()).to.be(firstPoint);
1150                 expect(chart.lockedIndicator()).to.be(null);
1151                 expect(indicatorChangeCalls).to.be.eql([[lastPoint.id, true], [firstPoint.id, false]]);
1152
1153                 expect(selectionChangeCount).to.be(0);
1154             })
1155         });
1156     });
1157
1158     it('should change the selection when the mouse cursor is dragged', () => {
1159         const context = new BrowsingContext();
1160         return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'InteractiveTimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
1161             const chart = createChartWithSampleCluster(context,
1162                 {selection: {lineStyle: '#f93', lineWidth: 2, fillStyle: '#ccc'}},
1163                 {interactiveChart: true, interactive: true});
1164
1165             chart.setDomain(sampleCluster.startTime, sampleCluster.endTime);
1166             chart.fetchMeasurementSets();
1167             respondWithSampleCluster(context.symbols.MockRemoteAPI.requests[0]);
1168
1169             const indicatorChangeCalls = [];
1170             chart.listenToAction('indicatorChange', (...args) => indicatorChangeCalls.push(args));
1171
1172             const selectionChangeCalls = [];
1173             chart.listenToAction('selectionChange', (...args) => selectionChangeCalls.push(args));
1174
1175             const zoomButton = chart.content('zoom-button');
1176
1177             let canvas;
1178             let rect;
1179             let y;
1180             let firstPoint;
1181             let oldRange;
1182             let newRange;
1183             return waitForComponentsToRender(context).then(() => {
1184                 expect(chart.currentSelection()).to.be(null);
1185                 expect(chart.currentPoint()).to.be(null);
1186                 expect(chart.lockedIndicator()).to.be(null);
1187                 expect(selectionChangeCalls).to.be.eql([]);
1188
1189                 canvas = chart.content().querySelector('canvas');
1190                 rect = canvas.getBoundingClientRect();
1191                 y = rect.top + rect.height / 2;
1192                 canvas.dispatchEvent(new MouseEvent('mousemove', {target: canvas, clientX: rect.left + 5, clientY: y, composed: true, bubbles: true}));
1193
1194                 CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
1195                 return waitForComponentsToRender(context);
1196             }).then(() => {
1197                 expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(true);
1198
1199                 firstPoint = chart.sampledTimeSeriesData('current').firstPoint();
1200                 expect(chart.currentSelection()).to.be(null);
1201                 expect(chart.currentPoint()).to.be(firstPoint);
1202                 expect(chart.lockedIndicator()).to.be(null);
1203                 expect(indicatorChangeCalls).to.be.eql([[firstPoint.id, false]]);
1204                 expect(zoomButton.offsetHeight).to.be(0);
1205
1206                 canvas.dispatchEvent(new MouseEvent('mousedown', {target: canvas, clientX: rect.left + 5, clientY: y, composed: true, bubbles: true}));
1207
1208                 CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
1209                 return waitForComponentsToRender(context);
1210             }).then(() => {
1211                 expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(false);
1212
1213                 expect(chart.currentSelection()).to.be(null);
1214                 expect(chart.currentPoint()).to.be(firstPoint);
1215                 expect(chart.lockedIndicator()).to.be(null);
1216                 expect(selectionChangeCalls).to.be.eql([]);
1217                 expect(indicatorChangeCalls).to.be.eql([[firstPoint.id, false]]);
1218                 expect(zoomButton.offsetHeight).to.be(0);
1219
1220                 canvas.dispatchEvent(new MouseEvent('mousemove', {target: canvas, clientX: rect.left + 15, clientY: y + 5, composed: true, bubbles: true}));
1221
1222                 CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
1223                 return waitForComponentsToRender(context);
1224             }).then(() => {
1225                 expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(true);
1226
1227                 expect(chart.currentSelection()).to.not.be(null);
1228                 expect(chart.currentPoint()).to.be(null);
1229                 expect(chart.lockedIndicator()).to.be(null);
1230                 expect(selectionChangeCalls.length).to.be(1);
1231                 oldRange = selectionChangeCalls[0][0];
1232                 expect(oldRange).to.be.eql(chart.currentSelection());
1233                 expect(selectionChangeCalls[0][1]).to.be(false);
1234                 expect(indicatorChangeCalls).to.be.eql([[firstPoint.id, false], [null, false]]);
1235                 expect(zoomButton.offsetHeight).to.be(0);
1236
1237                 canvas.dispatchEvent(new MouseEvent('mousemove', {target: canvas, clientX: rect.right - 5, clientY: y + 5, composed: true, bubbles: true}));
1238
1239                 CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
1240                 return waitForComponentsToRender(context);
1241             }).then(() => {
1242                 expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(true);
1243
1244                 expect(chart.currentSelection()).to.not.be(null);
1245                 expect(chart.currentPoint()).to.be(null);
1246                 expect(chart.lockedIndicator()).to.be(null);
1247                 expect(selectionChangeCalls.length).to.be(2);
1248                 newRange = selectionChangeCalls[1][0];
1249                 expect(newRange).to.be.eql(chart.currentSelection());
1250                 expect(newRange[0]).to.be(oldRange[0]);
1251                 expect(newRange[1]).to.be.greaterThan(oldRange[1]);
1252                 expect(selectionChangeCalls[1][1]).to.be(false);
1253                 expect(zoomButton.offsetHeight).to.be(0);
1254
1255                 canvas.dispatchEvent(new MouseEvent('mouseup', {target: canvas, clientX: rect.right - 5, clientY: y + 5, composed: true, bubbles: true}));
1256                 canvas.dispatchEvent(new MouseEvent('click', {target: canvas, clientX: rect.right - 5, clientY: y + 5, composed: true, bubbles: true}));
1257
1258                 CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
1259                 return waitForComponentsToRender(context);
1260             }).then(() => {
1261                 expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(true);
1262
1263                 expect(chart.currentSelection()).to.be.eql(newRange);
1264                 expect(chart.currentPoint()).to.be(null);
1265                 expect(chart.lockedIndicator()).to.be(null);
1266                 expect(selectionChangeCalls.length).to.be(3);
1267                 expect(selectionChangeCalls[2][0]).to.be.eql(newRange);
1268                 expect(selectionChangeCalls[2][1]).to.be(true);
1269                 expect(zoomButton.offsetHeight).to.be(0);
1270             });
1271         });
1272     });
1273
1274     it('should dispatch the "zoom" action when the zoom button is clicked', () => {
1275         const context = new BrowsingContext();
1276         return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'InteractiveTimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
1277             const chart = createChartWithSampleCluster(context,
1278                 {selection: {lineStyle: '#f93', lineWidth: 2, fillStyle: '#ccc'}, zoomButton: true},
1279                 {interactiveChart: true, interactive: true});
1280
1281             chart.setDomain(sampleCluster.startTime, sampleCluster.endTime);
1282             chart.fetchMeasurementSets();
1283             respondWithSampleCluster(context.symbols.MockRemoteAPI.requests[0]);
1284
1285             const zoomCalls = [];
1286             chart.listenToAction('zoom', (...args) => zoomCalls.push(args));
1287             const zoomButton = chart.content('zoom-button');
1288
1289             let selection;
1290             let canvas;
1291             return waitForComponentsToRender(context).then(() => {
1292                 expect(zoomButton.offsetHeight).to.be(0);
1293                 canvas = chart.content().querySelector('canvas');
1294                 const rect = canvas.getBoundingClientRect();
1295                 const y = rect.top + rect.height / 2;
1296                 canvas.dispatchEvent(new MouseEvent('mousemove', {target: canvas, clientX: rect.left + 5, clientY: y, composed: true, bubbles: true}));
1297                 canvas.dispatchEvent(new MouseEvent('mousedown', {target: canvas, clientX: rect.left + 5, clientY: y, composed: true, bubbles: true}));
1298                 canvas.dispatchEvent(new MouseEvent('mousemove', {target: canvas, clientX: rect.right - 10, clientY: y + 5, composed: true, bubbles: true}));
1299                 canvas.dispatchEvent(new MouseEvent('mouseup', {target: canvas, clientX: rect.right - 10, clientY: y + 5, composed: true, bubbles: true}));
1300
1301                 CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
1302                 return waitForComponentsToRender(context);
1303             }).then(() => {
1304                 expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(true);
1305
1306                 selection = chart.currentSelection();
1307                 expect(selection).to.not.be(null);
1308                 expect(chart.currentPoint()).to.be(null);
1309                 expect(chart.lockedIndicator()).to.be(null);
1310                 expect(zoomButton.offsetHeight).to.not.be(0);
1311                 expect(zoomCalls).to.be.eql([]);
1312                 zoomButton.click();
1313             }).then(() => {
1314                 expect(zoomCalls).to.be.eql([[selection]]);
1315
1316                 CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
1317                 return waitForComponentsToRender(context);
1318             }).then(() => {
1319                 expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(false);
1320             });
1321         });
1322     });
1323
1324     it('should clear the selection when clicked', () => {
1325         const context = new BrowsingContext();
1326         return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'InteractiveTimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
1327             const chart = createChartWithSampleCluster(context,
1328                 {selection: {lineStyle: '#f93', lineWidth: 2, fillStyle: '#ccc'}},
1329                 {interactiveChart: true, interactive: true});
1330
1331             chart.setDomain(sampleCluster.startTime, sampleCluster.endTime);
1332             chart.fetchMeasurementSets();
1333             respondWithSampleCluster(context.symbols.MockRemoteAPI.requests[0]);
1334
1335             let canvas;
1336             let rect;
1337             let y;
1338             return waitForComponentsToRender(context).then(() => {
1339                 canvas = chart.content().querySelector('canvas');
1340                 rect = canvas.getBoundingClientRect();
1341                 y = rect.top + rect.height / 2;
1342                 canvas.dispatchEvent(new MouseEvent('mousemove', {target: canvas, clientX: rect.left + 5, clientY: y, composed: true, bubbles: true}));
1343                 canvas.dispatchEvent(new MouseEvent('mousedown', {target: canvas, clientX: rect.left + 5, clientY: y, composed: true, bubbles: true}));
1344                 canvas.dispatchEvent(new MouseEvent('mousemove', {target: canvas, clientX: rect.right - 10, clientY: y + 5, composed: true, bubbles: true}));
1345                 canvas.dispatchEvent(new MouseEvent('mouseup', {target: canvas, clientX: rect.right - 10, clientY: y + 5, composed: true, bubbles: true}));
1346                 canvas.dispatchEvent(new MouseEvent('click', {target: canvas, clientX: rect.right - 10, clientY: y + 5, composed: true, bubbles: true}));
1347
1348                 CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
1349                 return waitForComponentsToRender(context);
1350             }).then(() => {
1351                 expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(true);
1352
1353                 expect(chart.currentSelection()).to.not.be(null);
1354                 expect(chart.currentPoint()).to.be(null);
1355                 expect(chart.lockedIndicator()).to.be(null);
1356
1357                 canvas.dispatchEvent(new MouseEvent('click', {target: canvas, clientX: rect.left + 1, clientY: y + 5, composed: true, bubbles: true}));
1358
1359                 CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
1360                 return waitForComponentsToRender(context);
1361             }).then(() => {
1362                 expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(true);
1363
1364                 expect(chart.currentSelection()).to.be(null);
1365                 expect(chart.currentPoint()).to.be(chart.sampledTimeSeriesData('current').firstPoint());
1366                 expect(chart.lockedIndicator()).to.be(null);
1367             });
1368         });
1369     });
1370
1371     it('should dispatch "annotationClick" action when an annotation is clicked', () => {
1372         const context = new BrowsingContext();
1373         return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'InteractiveTimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => {
1374             const options = {annotations: {
1375                 textStyle: '#000',
1376                 textBackground: '#fff',
1377                 minWidth: 3,
1378                 barHeight: 10,
1379                 barSpacing: 1}};
1380             const chart = createChartWithSampleCluster(context, options, {interactiveChart: true});
1381
1382             chart.setDomain(sampleCluster.startTime, sampleCluster.endTime);
1383             chart.fetchMeasurementSets();
1384             respondWithSampleCluster(context.symbols.MockRemoteAPI.requests[0]);
1385
1386             const diff = sampleCluster.endTime - sampleCluster.startTime;
1387             const annotations = [{
1388                 startTime: sampleCluster.startTime + diff / 2,
1389                 endTime: sampleCluster.endTime - diff / 4,
1390                 label: 'hello, world',
1391                 fillStyle: 'rgb(0, 0, 255)',
1392             }]
1393             chart.setAnnotations(annotations);
1394
1395             const annotationClickCalls = [];
1396             chart.listenToAction('annotationClick', (...args) => annotationClickCalls.push(args));
1397
1398             let canvas;
1399             let init;
1400             return waitForComponentsToRender(context).then(() => {
1401                 expect(annotationClickCalls).to.be.eql([]);
1402                 expect(chart.content('annotation-label').textContent).to.not.contain('hello, world');
1403
1404                 canvas = chart.content().querySelector('canvas');
1405                 const rect = canvas.getBoundingClientRect();
1406                 init = {target: canvas, clientX: rect.right - rect.width / 4, clientY: rect.bottom - 5, composed: true, bubbles: true};
1407                 canvas.dispatchEvent(new MouseEvent('mousemove', init));
1408
1409                 CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
1410                 return waitForComponentsToRender(context);
1411             }).then(() => {
1412                 expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(true);
1413
1414                 expect(chart.content('annotation-label').textContent).to.contain('hello, world');
1415                 expect(annotationClickCalls).to.be.eql([]);
1416                 canvas.dispatchEvent(new MouseEvent('mousedown', init));
1417                 canvas.dispatchEvent(new MouseEvent('mouseup', init));
1418                 canvas.dispatchEvent(new MouseEvent('click', init));
1419
1420                 expect(annotationClickCalls).to.be.eql([[annotations[0]]]);
1421
1422                 CanvasTest.fillCanvasBeforeRedrawCheck(canvas);
1423                 return waitForComponentsToRender(context);
1424             }).then(() => {
1425                 expect(CanvasTest.hasCanvasBeenRedrawn(canvas)).to.be(false);
1426             });
1427         });
1428     });
1429
1430 });