1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Include test fixture.
6GEN_INCLUDE(['net_internals_test.js']);
7
8// Anonymous namespace
9(function() {
10
11// Range of time set on a log once loaded used by sanity checks.
12// Used by sanityCheckWithTimeRange.
13var startTime = null;
14var endTime = null;
15
16function timelineView() {
17  return TimelineView.getInstance();
18}
19
20function graphView() {
21  return timelineView().graphView_;
22}
23
24function scrollbar() {
25  return graphView().scrollbar_;
26}
27
28function canvas() {
29  return graphView().canvas_;
30}
31
32/**
33 * A Task that creates a log dump, modifies it so |timeTicks| are all in UTC,
34 * clears all events from the log, and then adds two new SOCKET events, which
35 * have the specified start and end times.
36 *
37 * Most of these tests start with this task first.  This gives us a known
38 * starting state, and prevents the data from automatically updating.
39 *
40 * @param {int} startTime Time of the begin event.
41 * @param {int} endTime Time of the end event.
42 * @extends {NetInternalsTest.Task}
43 */
44function LoadLogWithNewEventsTask(startTime, endTime) {
45  NetInternalsTest.Task.call(this);
46  this.startTime_ = startTime;
47  this.endTime_ = endTime;
48}
49
50LoadLogWithNewEventsTask.prototype = {
51  __proto__: NetInternalsTest.Task.prototype,
52
53  /**
54   * Starts creating a log dump.
55   */
56  start: function() {
57    log_util.createLogDumpAsync('test', this.onLogDumpCreated.bind(this), true);
58  },
59
60  /**
61   * Modifies the log dump and loads it.
62   */
63  onLogDumpCreated: function(logDumpText) {
64    var logDump = JSON.parse(logDumpText);
65
66    logDump.constants.timeTickOffset = '0';
67    logDump.events = [];
68
69    var source = new NetInternalsTest.Source(1, EventSourceType.SOCKET);
70    logDump.events.push(
71        NetInternalsTest.createBeginEvent(source, EventType.SOCKET_ALIVE,
72                                          this.startTime_, null));
73    logDump.events.push(
74        NetInternalsTest.createMatchingEndEvent(logDump.events[0],
75                                                this.endTime_, null));
76    logDumpText = JSON.stringify(logDump);
77
78    assertEquals('Log loaded.', log_util.loadLogFile(logDumpText));
79
80    endTime = this.endTime_;
81    startTime = this.startTime_;
82    if (startTime >= endTime)
83      --startTime;
84
85    sanityCheckWithTimeRange(false);
86
87    this.onTaskDone();
88  }
89};
90
91/**
92 * Checks certain invariant properties of the TimelineGraphView and the
93 * scroll bar.
94 */
95function sanityCheck() {
96  expectLT(graphView().startTime_, graphView().endTime_);
97  expectLE(0, scrollbar().getPosition());
98  expectLE(scrollbar().getPosition(), scrollbar().getRange());
99}
100
101/**
102 * Checks what sanityCheck does, but also checks that |startTime| and |endTime|
103 * are the same as those used by the graph, as well as whether we have a timer
104 * running to update the graph's end time.  To avoid flake, this should only
105 * be used synchronously relative to when |startTime| and |endTime| were set,
106 * unless |expectUpdateTimer| is false.
107 * @param {bool} expectUpdateTimer true if the TimelineView should currently
108 *     have an update end time timer running.
109 */
110function sanityCheckWithTimeRange(expectUpdateTimer) {
111  if (!expectUpdateTimer) {
112    expectEquals(null, timelineView().updateIntervalId_);
113  } else {
114    expectNotEquals(null, timelineView().updateIntervalId_);
115  }
116  assertNotEquals(startTime, null);
117  assertNotEquals(endTime, null);
118  expectEquals(startTime, graphView().startTime_);
119  expectEquals(endTime, graphView().endTime_);
120  sanityCheck(false);
121}
122
123/**
124 * Checks what sanityCheck does, but also checks that |startTime| and |endTime|
125 * are the same as those used by the graph.
126 */
127function sanityCheckNotUpdating() {
128  expectEquals(null, timelineView().updateIntervalId_);
129  sanityCheckWithTimeRange();
130}
131
132/**
133 * Simulates mouse wheel movement over the canvas element.
134 * @param {number} ticks Number of mouse wheel ticks to simulate.
135 */
136function mouseZoom(ticks) {
137  var scrollbarStartedAtEnd =
138      (scrollbar().getRange() == scrollbar().getPosition());
139
140  var event = new WheelEvent('mousewheel', {deltaX: 0, deltaY: -ticks});
141  canvas().dispatchEvent(event);
142
143  // If the scrollbar started at the end of the range, make sure it ends there
144  // as well.
145  if (scrollbarStartedAtEnd)
146    expectEquals(scrollbar().getRange(), scrollbar().getPosition());
147
148  sanityCheck();
149}
150
151/**
152 * Simulates moving the mouse wheel up.
153 * @param {number} ticks Number of mouse wheel ticks to simulate.
154 */
155function mouseZoomIn(ticks) {
156  assertGT(ticks, 0);
157  var oldScale = graphView().scale_;
158  var oldRange = scrollbar().getRange();
159
160  mouseZoom(ticks);
161
162  if (oldScale == graphView().scale_) {
163    expectEquals(oldScale, TimelineGraphView.MIN_SCALE);
164  } else {
165    expectLT(graphView().scale_, oldScale);
166  }
167  expectGE(scrollbar().getRange(), oldRange);
168}
169
170/**
171 * Simulates moving the mouse wheel down.
172 * @param {number} ticks Number of mouse wheel ticks to simulate.
173 */
174function mouseZoomOut(ticks) {
175  assertGT(ticks, 0);
176  var oldScale = graphView().scale_;
177  var oldRange = scrollbar().getRange();
178
179  mouseZoom(-ticks);
180
181  expectGT(graphView().scale_, oldScale);
182  expectLE(scrollbar().getRange(), oldRange);
183}
184
185/**
186 * Simulates zooming all the way with multiple mouse wheel events.
187 */
188function mouseZoomAllTheWayIn() {
189  expectLT(TimelineGraphView.MIN_SCALE, graphView().scale_);
190  while (graphView().scale_ != TimelineGraphView.MIN_SCALE)
191    mouseZoomIn(8);
192  // Verify that zooming in when already at max zoom works.
193  mouseZoomIn(1);
194}
195
196/**
197 * A Task that scrolls the scrollbar by manipulating the DOM, and then waits
198 * for the scroll to complete.  Has to be a task because onscroll and DOM
199 * manipulations both occur asynchronously.
200 *
201 * Not safe to use when other asynchronously running code may try to
202 * manipulate the scrollbar itself, or adjust the length of the scrollbar.
203 *
204 * @param {int} position Position to scroll to.
205 * @extends {NetInternalsTest.Task}
206 */
207function MouseScrollTask(position) {
208  NetInternalsTest.Task.call(this);
209  this.position_ = position;
210  // If the scrollbar's |position| and its node's |scrollLeft| values don't
211  // currently match, we set this to true and wait for |scrollLeft| to be
212  // updated, which will trigger an onscroll event.
213  this.waitingToStart_ = false;
214}
215
216MouseScrollTask.prototype = {
217  __proto__: NetInternalsTest.Task.prototype,
218
219  start: function() {
220    this.waitingToStart_ = false;
221    // If the scrollbar is already in the correct position, do nothing.
222    if (scrollbar().getNode().scrollLeft == this.position_) {
223      // We may still have a timer going to adjust the position of the
224      // scrollbar to some other value.  If so, this will clear it.
225      scrollbar().setPosition(this.position_);
226      this.onTaskDone();
227      return;
228    }
229
230    // Replace the onscroll event handler with our own.
231    this.oldOnScroll_ = scrollbar().getNode().onscroll;
232    scrollbar().getNode().onscroll = this.onScroll_.bind(this);
233    if (scrollbar().getNode().scrollLeft != scrollbar().getPosition()) {
234      this.waitingToStart_ = true;
235      return;
236    }
237
238    window.setTimeout(this.startScrolling_.bind(this), 0);
239  },
240
241  onScroll_: function(event) {
242    // Restore the original onscroll function.
243    scrollbar().getNode().onscroll = this.oldOnScroll_;
244    // Call the original onscroll function.
245    this.oldOnScroll_(event);
246
247    if (this.waitingToStart_) {
248      this.start();
249      return;
250    }
251
252    assertEquals(this.position_, scrollbar().getNode().scrollLeft);
253    assertEquals(this.position_, scrollbar().getPosition());
254
255    sanityCheck();
256    this.onTaskDone();
257  },
258
259  startScrolling_: function() {
260    scrollbar().getNode().scrollLeft = this.position_;
261  }
262};
263
264/**
265 * Tests setting and updating range.
266 */
267TEST_F('NetInternalsTest', 'netInternalsTimelineViewRange', function() {
268  NetInternalsTest.switchToView('timeline');
269
270  // Set startTime/endTime for sanity checks.
271  startTime = graphView().startTime_;
272  endTime = graphView().endTime_;
273  sanityCheckWithTimeRange(true);
274
275  startTime = 0;
276  endTime = 10;
277  graphView().setDateRange(new Date(startTime), new Date(endTime));
278  sanityCheckWithTimeRange(true);
279
280  endTime = (new Date()).getTime();
281  graphView().updateEndDate();
282
283  expectGE(graphView().endTime_, endTime);
284  sanityCheck();
285
286  testDone();
287});
288
289/**
290 * Tests using the scroll bar.
291 */
292TEST_F('NetInternalsTest', 'netInternalsTimelineViewScrollbar', function() {
293  // The range we want the graph to have.
294  var expectedGraphRange = canvas().width;
295
296  function checkGraphRange() {
297    expectEquals(expectedGraphRange, scrollbar().getRange());
298  }
299
300  var taskQueue = new NetInternalsTest.TaskQueue(true);
301  // Load a log and then switch to the timeline view.  The end time is
302  // calculated so that the range is exactly |expectedGraphRange|.
303  taskQueue.addTask(
304      new LoadLogWithNewEventsTask(
305          55,
306          55 + graphView().scale_ * (canvas().width + expectedGraphRange)));
307  taskQueue.addFunctionTask(
308      NetInternalsTest.switchToView.bind(null, 'timeline'));
309  taskQueue.addFunctionTask(checkGraphRange);
310
311  taskQueue.addTask(new MouseScrollTask(0));
312  taskQueue.addTask(new MouseScrollTask(expectedGraphRange));
313  taskQueue.addTask(new MouseScrollTask(1));
314  taskQueue.addTask(new MouseScrollTask(expectedGraphRange - 1));
315
316  taskQueue.addFunctionTask(checkGraphRange);
317  taskQueue.addFunctionTask(sanityCheckWithTimeRange.bind(null, false));
318  taskQueue.run();
319});
320
321/**
322 * Dumps a log file to memory, modifies its events, loads it again, and
323 * makes sure the range is correctly set and not automatically updated.
324 */
325TEST_F('NetInternalsTest', 'netInternalsTimelineViewLoadLog', function() {
326  // After loading the log file, the rest of the test runs synchronously.
327  function testBody() {
328    NetInternalsTest.switchToView('timeline');
329    sanityCheckWithTimeRange(false);
330
331    // Make sure everything's still fine when we switch to another view.
332    NetInternalsTest.switchToView('events');
333    sanityCheckWithTimeRange(false);
334  }
335
336  // Load a log and then run the rest of the test.
337  var taskQueue = new NetInternalsTest.TaskQueue(true);
338  taskQueue.addTask(new LoadLogWithNewEventsTask(55, 10055));
339  taskQueue.addFunctionTask(testBody);
340  taskQueue.run();
341});
342
343/**
344 * Zooms out twice, and then zooms in once.
345 */
346TEST_F('NetInternalsTest', 'netInternalsTimelineViewZoomOut', function() {
347  // After loading the log file, the rest of the test runs synchronously.
348  function testBody() {
349    NetInternalsTest.switchToView('timeline');
350    mouseZoomOut(1);
351    mouseZoomOut(1);
352    mouseZoomIn(1);
353    sanityCheckWithTimeRange(false);
354  }
355
356  // Load a log and then run the rest of the test.
357  var taskQueue = new NetInternalsTest.TaskQueue(true);
358  taskQueue.addTask(new LoadLogWithNewEventsTask(55, 10055));
359  taskQueue.addFunctionTask(testBody);
360  taskQueue.run();
361});
362
363/**
364 * Zooms in as much as allowed, and zooms out once.
365 */
366TEST_F('NetInternalsTest', 'netInternalsTimelineViewZoomIn', function() {
367  // After loading the log file, the rest of the test runs synchronously.
368  function testBody() {
369    NetInternalsTest.switchToView('timeline');
370    mouseZoomAllTheWayIn();
371    mouseZoomOut(1);
372    sanityCheckWithTimeRange(false);
373  }
374
375  // Load a log and then run the rest of the test.
376  var taskQueue = new NetInternalsTest.TaskQueue(true);
377  taskQueue.addTask(new LoadLogWithNewEventsTask(55, 10055));
378  taskQueue.addFunctionTask(testBody);
379  taskQueue.run();
380});
381
382/**
383 * Tests case of all events having the same time.
384 */
385TEST_F('NetInternalsTest', 'netInternalsTimelineViewDegenerate', function() {
386  // After loading the log file, the rest of the test runs synchronously.
387  function testBody() {
388    NetInternalsTest.switchToView('timeline');
389    mouseZoomOut(1);
390    mouseZoomAllTheWayIn();
391    mouseZoomOut(1);
392    sanityCheckWithTimeRange(false);
393  }
394
395  // Load a log and then run the rest of the test.
396  var taskQueue = new NetInternalsTest.TaskQueue(true);
397  taskQueue.addTask(new LoadLogWithNewEventsTask(55, 55));
398  taskQueue.addFunctionTask(testBody);
399  taskQueue.run();
400});
401
402/**
403 * Tests case of having no events.  Runs synchronously.
404 */
405TEST_F('NetInternalsTest', 'netInternalsTimelineViewNoEvents', function() {
406  // Click the button to clear all the captured events, and then switch to
407  // timeline
408  $(CaptureView.RESET_BUTTON_ID).click();
409  NetInternalsTest.switchToView('timeline');
410
411  // Set startTime/endTime for sanity checks.
412  startTime = graphView().startTime_;
413  endTime = graphView().endTime_;
414
415  sanityCheckWithTimeRange(true);
416
417  mouseZoomOut(1);
418  sanityCheckWithTimeRange(true);
419
420  mouseZoomAllTheWayIn();
421  sanityCheckWithTimeRange(true);
422
423  mouseZoomOut(1);
424  sanityCheckWithTimeRange(true);
425
426  testDone();
427});
428
429})();  // Anonymous namespace
430