forked from firefox-devtools/profiler
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Viewport.test.js
752 lines (659 loc) · 25.4 KB
/
Viewport.test.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// @flow
import * as React from 'react';
import { Provider } from 'react-redux';
import { fireEvent } from '@testing-library/react';
import { render, act } from 'firefox-profiler/test/fixtures/testing-library';
import { withChartViewport } from '../../components/shared/chart/Viewport';
import {
getCommittedRange,
getPreviewSelection,
} from '../../selectors/profile';
import { changeSidebarOpenState } from '../../actions/app';
import explicitConnect from '../../utils/connect';
import { ensureExists } from '../../utils/flow';
import { autoMockCanvasContext } from '../fixtures/mocks/canvas-context';
import {
autoMockElementSize,
setMockedElementSize,
} from '../fixtures/mocks/element-size';
import { mockRaf } from '../fixtures/mocks/request-animation-frame';
import { storeWithProfile } from '../fixtures/stores';
import { getProfileFromTextSamples } from '../fixtures/profiles/processed-profile';
import type {
Milliseconds,
MixedObject,
PreviewSelection,
StartEndRange,
} from 'firefox-profiler/types';
// The following define the magic values used for the mocked bounding box of the
// the rendered component.
const BOUNDING_BOX_WIDTH = 300;
const BOUNDING_BOX_HEIGHT = 100;
const BOUNDING_BOX_LEFT = 5;
const BOUNDING_BOX_TOP = 7;
const INITIAL_ELEMENT_SIZE = {
width: BOUNDING_BOX_WIDTH,
height: BOUNDING_BOX_HEIGHT,
offsetX: BOUNDING_BOX_LEFT,
offsetY: BOUNDING_BOX_TOP,
};
// The maximum zoom is required by the viewportProps, and is defined as 0.1 to be
// a reasonable (but arbitrary) limit to zoom in.
const MAXIMUM_ZOOM = 0.1;
// Various tests assert the behavior of the viewport depending on whether it's larger
// or smaller than the bounding box. Provide a larger viewport height, and a smaller
// one.
const MAX_VIEWPORT_HEIGHT = BOUNDING_BOX_HEIGHT * 3;
const SMALL_MAX_VIEWPORT_HEIGHT = BOUNDING_BOX_HEIGHT * 0.2;
describe('Viewport', function () {
autoMockCanvasContext();
autoMockElementSize(INITIAL_ELEMENT_SIZE);
it('matches the component snapshot', () => {
const { container, unmount } = setup();
expect(container.firstChild).toMatchSnapshot();
// Trigger any unmounting behavior handlers, just make sure it doesn't
// throw any errors.
unmount();
});
it('renders the wrapped chart component', () => {
const { container } = setup();
expect(container.querySelector('#dummy-chart')).toBeTruthy();
});
it('provides a viewport', () => {
const { getChartViewport } = setup();
const viewport = getChartViewport();
expect(viewport).toEqual({
containerWidth: BOUNDING_BOX_WIDTH,
containerHeight: BOUNDING_BOX_HEIGHT,
viewportLeft: 0,
viewportRight: 1,
viewportTop: 0,
viewportBottom: BOUNDING_BOX_HEIGHT,
isDragging: false,
isSizeSet: true,
// Just pass the function through.
moveViewport: viewport.moveViewport,
});
});
function testScrollingHint(eventOptions) {
const { getByText, scroll } = setup();
const isTimerVisible = () =>
!getByText(/Zoom Chart/).classList.contains('hidden');
// No hint is shown at the beginning.
expect(isTimerVisible()).toBe(false);
// Zoom in a bit.
scroll({ deltaY: 10, ...eventOptions });
expect(isTimerVisible()).toBe(false);
// Now scroll, no hint should show.
scroll({ deltaY: 10 });
expect(isTimerVisible()).toBe(false);
}
describe('scrolling hint', function () {
it('can show a scrolling hint', function () {
jest.useFakeTimers();
const { getByText, scroll } = setup();
const isTimerVisible = () =>
!getByText(/Zoom Chart/).classList.contains('hidden');
// No hint is shown.
expect(isTimerVisible()).toBe(false);
// Scroll a bit to show the menu
scroll({ deltaY: 10 });
expect(isTimerVisible()).toBe(true);
// Run the setTimeout, the menu should disappear.
jest.runAllTimers();
expect(isTimerVisible()).toBe(false);
});
// testScrollingHint has assertions.
// eslint-disable-next-line jest/expect-expect
it('will not show a ctrl scrolling hint after zooming once', function () {
testScrollingHint({ ctrlKey: true });
});
// testScrollingHint has assertions.
// eslint-disable-next-line jest/expect-expect
it('will not show a shift scrolling hint after zooming once', function () {
testScrollingHint({ shiftKey: true });
});
});
describe('scrolling up and down', function () {
it('scrolls the viewport down using the mousewheel', () => {
const { scrollAndGetViewport, getChartViewport } = setup();
expect(getChartViewport()).toMatchObject({
viewportLeft: 0,
viewportRight: 1,
viewportTop: 0,
viewportBottom: BOUNDING_BOX_HEIGHT,
});
const deltaY = 10;
expect(scrollAndGetViewport({ deltaY: 10 })).toMatchObject({
viewportLeft: 0,
viewportRight: 1,
viewportTop: deltaY,
viewportBottom: BOUNDING_BOX_HEIGHT + deltaY,
});
});
it('cannot scroll past the bottom of the viewport', () => {
const { scrollAndGetViewport } = setup();
expect(scrollAndGetViewport({ deltaY: 1000 })).toMatchObject({
viewportLeft: 0,
viewportRight: 1,
viewportTop: MAX_VIEWPORT_HEIGHT - BOUNDING_BOX_HEIGHT,
viewportBottom: MAX_VIEWPORT_HEIGHT,
});
});
it('cannot scroll past the top of the viewport', () => {
const { scrollAndGetViewport } = setup();
expect(scrollAndGetViewport({ deltaY: -100 })).toMatchObject({
viewportLeft: 0,
viewportRight: 1,
viewportTop: 0,
viewportBottom: BOUNDING_BOX_HEIGHT,
});
});
});
describe('mousewheel zooming using ctrl + mousewheel', function () {
// These series of tests are very particular to margin differences, so check
// different margin combinations separately.
const marginTests = [
{ marginLeft: 0, marginRight: 0, description: 'no margins' },
{ marginLeft: 33, marginRight: 0, description: 'with a left margin' },
{ marginLeft: 0, marginRight: 33, description: 'with a right margin' },
{ marginLeft: 33, marginRight: 55, description: 'with both margins' },
];
// Run through the tests.
for (const { marginLeft, marginRight, description } of marginTests) {
describe(description, function () {
// Take into account the margins and the component width to find the inner
// component width, which is the active viewport area. All mouse events should
// be relative to this space.
const innerComponentWidth =
BOUNDING_BOX_WIDTH - marginLeft - marginRight;
// | Viewport |
// |-----------*------------|
// ^
// Mouse position
it('zooms the preview selection equally when the mouse is centered', () => {
const { scrollAndGetViewport } = setup({
marginLeft,
marginRight,
});
// Note that clientX will get rounded, so it won't be _exactly_ at the
// center, which is why we do some approximations later.
const viewport = scrollAndGetViewport({
deltaY: -100,
ctrlKey: true,
clientX: BOUNDING_BOX_LEFT + innerComponentWidth * 0.5 + marginLeft,
});
// Assert that this zooms in equally.
expect(viewport.viewportLeft).toBeGreaterThan(0);
expect(viewport.viewportRight).toBeLessThan(1);
expect(viewport.viewportLeft + viewport.viewportRight).toBeCloseTo(1);
// Only do an additional viewport top/bottom check here.
expect(viewport).toMatchObject({
viewportTop: 0,
viewportBottom: BOUNDING_BOX_HEIGHT,
});
});
// | Viewport |
// |------------------*-----|
// ^
// Mouse position
it('zooms the preview selection in a direction when the mouse is to the right', () => {
const { scrollAndGetViewport } = setup({
marginLeft,
marginRight,
});
const viewport = scrollAndGetViewport({
deltaY: -10,
ctrlKey: true,
clientX:
BOUNDING_BOX_LEFT + innerComponentWidth * 0.75 + marginLeft,
});
// Assert that the left hand side zooms in more.
const changeInLeft = viewport.viewportLeft;
const changeInRight = 1 - viewport.viewportRight;
expect(viewport.viewportLeft).toBeGreaterThan(0);
expect(viewport.viewportRight).toBeLessThan(1);
expect(changeInLeft).toBeGreaterThan(changeInRight);
});
// | Viewport |
// |----*-------------------|
// ^
// Mouse position
it('zooms the preview selection in a direction when the mouse is to the left', () => {
const { scrollAndGetViewport } = setup({
marginLeft,
marginRight,
});
const viewport = scrollAndGetViewport({
deltaY: -10,
ctrlKey: true,
clientX:
BOUNDING_BOX_LEFT + innerComponentWidth * 0.25 + marginLeft,
});
// Assert that the left hand side zooms in more.
const changeInLeft = viewport.viewportLeft;
const changeInRight = 1 - viewport.viewportRight;
expect(viewport.viewportLeft).toBeGreaterThan(0);
expect(viewport.viewportRight).toBeLessThan(1);
expect(changeInLeft).toBeLessThan(changeInRight);
});
// | Viewport |
// |*-----------------------|
// ^
// Mouse position
it('does not scroll the viewport left when the mouse is centered on the left.', () => {
const { scrollAndGetViewport } = setup({
marginLeft,
marginRight,
});
const viewport = scrollAndGetViewport({
deltaY: -10,
ctrlKey: true,
clientX: BOUNDING_BOX_LEFT + marginLeft,
});
expect(viewport.viewportLeft).toBe(0);
expect(viewport.viewportRight).toBeLessThan(1);
});
// | Viewport |
// |-----------------------*|
// ^
// Mouse position
it('does not scroll the viewport right when the mouse is centered on the right.', () => {
const { scrollAndGetViewport } = setup({
marginLeft,
marginRight,
});
const viewport = scrollAndGetViewport({
deltaY: -10,
ctrlKey: true,
clientX: BOUNDING_BOX_LEFT + innerComponentWidth + marginLeft,
});
expect(viewport.viewportLeft).toBeGreaterThan(0);
expect(viewport.viewportRight).toBe(1);
});
it('cannot zoom out beyond the bounds', () => {
const { scrollAndGetViewport } = setup({
marginLeft,
marginRight,
});
const viewport = scrollAndGetViewport({
deltaY: 100,
ctrlKey: true,
clientX: BOUNDING_BOX_LEFT + innerComponentWidth * 0.5 + marginLeft,
});
expect(viewport).toMatchObject({
viewportLeft: 0,
viewportRight: 1,
});
});
});
}
});
describe('dragging around', function () {
const middleX = BOUNDING_BOX_WIDTH * 0.5;
const middleY = BOUNDING_BOX_HEIGHT * 0.5;
it('can click and drag down', function () {
const { getChartViewport, clickAndDrag } = setup();
expect(getChartViewport()).toMatchObject({
viewportLeft: 0,
viewportRight: 1,
viewportTop: 0,
viewportBottom: BOUNDING_BOX_HEIGHT,
});
clickAndDrag(middleX, middleY, middleX, middleY - 50);
expect(getChartViewport()).toMatchObject({
viewportLeft: 0,
viewportRight: 1,
viewportTop: 50,
viewportBottom: BOUNDING_BOX_HEIGHT + 50,
});
});
it('can click and drag up', function () {
const { getChartViewport, clickAndDrag } = setup();
const middleX = BOUNDING_BOX_WIDTH * 0.5;
const middleY = BOUNDING_BOX_HEIGHT * 0.5;
clickAndDrag(middleX, middleY, middleX, middleY - 100);
expect(getChartViewport()).toMatchObject({
viewportLeft: 0,
viewportRight: 1,
viewportTop: 100,
viewportBottom: BOUNDING_BOX_HEIGHT + 100,
});
clickAndDrag(middleX, middleY, middleX, middleY + 50);
expect(getChartViewport()).toMatchObject({
viewportLeft: 0,
viewportRight: 1,
viewportTop: 50,
viewportBottom: BOUNDING_BOX_HEIGHT + 50,
});
});
it('will not drag beyond the top of the window', function () {
const { getChartViewport, clickAndDrag } = setup();
const middleX = BOUNDING_BOX_WIDTH * 0.5;
const middleY = BOUNDING_BOX_HEIGHT * 0.5;
clickAndDrag(middleX, middleY, middleX, middleY + 50);
expect(getChartViewport()).toMatchObject({
viewportLeft: 0,
viewportRight: 1,
viewportTop: 0,
viewportBottom: BOUNDING_BOX_HEIGHT,
});
});
it('will not drag beyond the bottom of the window', function () {
const { getChartViewport, clickAndDrag } = setup();
const middleX = BOUNDING_BOX_WIDTH * 0.5;
const middleY = BOUNDING_BOX_HEIGHT * 0.5;
clickAndDrag(
middleX,
middleY,
middleX,
middleY - MAX_VIEWPORT_HEIGHT * 2
);
expect(getChartViewport()).toMatchObject({
viewportLeft: 0,
viewportRight: 1,
viewportTop: MAX_VIEWPORT_HEIGHT - BOUNDING_BOX_HEIGHT,
viewportBottom: MAX_VIEWPORT_HEIGHT,
});
});
it('will place the contents of a small viewport at the top of the bounding box', function () {
const { getChartViewport, clickAndDrag } = setup({
maxViewportHeight: SMALL_MAX_VIEWPORT_HEIGHT,
startsAtBottom: false,
});
const anchoredViewport = {
viewportLeft: 0,
viewportRight: 1,
viewportTop: 0,
viewportBottom: BOUNDING_BOX_HEIGHT,
};
expect(getChartViewport()).toMatchObject(anchoredViewport);
// It will not move the viewport.
clickAndDrag(middleX, middleY, middleX, middleY - 10);
expect(getChartViewport()).toMatchObject(anchoredViewport);
clickAndDrag(middleX, middleY, middleX, middleY + 10);
expect(getChartViewport()).toMatchObject(anchoredViewport);
});
it('will place the contents of a small viewport at the bottom of the bounding box', function () {
const { getChartViewport, clickAndDrag } = setup({
maxViewportHeight: SMALL_MAX_VIEWPORT_HEIGHT,
startsAtBottom: true,
});
const anchoredViewport = {
viewportLeft: 0,
viewportRight: 1,
viewportTop: SMALL_MAX_VIEWPORT_HEIGHT - BOUNDING_BOX_HEIGHT,
viewportBottom: SMALL_MAX_VIEWPORT_HEIGHT,
};
expect(getChartViewport()).toMatchObject(anchoredViewport);
// It will not move the viewport.
clickAndDrag(middleX, middleY, middleX, middleY - 10);
expect(getChartViewport()).toMatchObject(anchoredViewport);
clickAndDrag(middleX, middleY, middleX, middleY + 10);
expect(getChartViewport()).toMatchObject(anchoredViewport);
});
it('can click and drag left/right', function () {
const { scrollAndGetViewport, getChartViewport, clickAndDrag } = setup();
// Assert the initial values.
const { viewportLeft, viewportRight } = scrollAndGetViewport({
// Zoom in some large arbitrary amount:
deltaY: -5000,
ctrlKey: true,
clientX: BOUNDING_BOX_LEFT + BOUNDING_BOX_WIDTH * 0.5,
});
// These values are arbitrary, but show that the viewport was zoomed in.
expect(viewportLeft).toBeGreaterThan(0.3);
expect(viewportRight).toBeLessThan(0.7);
// Perform the dragging action.
clickAndDrag(middleX, middleY, middleX + 500, middleY);
expect(getChartViewport().viewportLeft).toBeLessThan(viewportLeft);
expect(getChartViewport().viewportRight).toBeLessThan(viewportRight);
// Drag back the other way.
clickAndDrag(middleX, middleY, middleX - 1000, middleY);
expect(getChartViewport().viewportLeft).toBeGreaterThan(viewportLeft);
expect(getChartViewport().viewportRight).toBeGreaterThan(viewportRight);
});
it('will not scroll off to the left of the viewport bounds', function () {
const { scroll, getChartViewport, clickAndDrag } = setup();
scroll({
// Zoom in some large arbitrary amount:
deltaY: -5000,
ctrlKey: true,
clientX: BOUNDING_BOX_LEFT + BOUNDING_BOX_WIDTH * 0.5,
});
// Perform the dragging action some arbitrarily large distance..
clickAndDrag(middleX, middleY, middleX + 10000, middleY);
expect(getChartViewport().viewportLeft).toBe(0);
expect(getChartViewport().viewportRight).toBeLessThan(0.4);
});
it('will not scroll off to the right of the viewport bounds', function () {
const { scroll, getChartViewport, clickAndDrag } = setup();
scroll({
// Zoom in some large arbitrary amount:
deltaY: -5000,
ctrlKey: true,
clientX: BOUNDING_BOX_LEFT + BOUNDING_BOX_WIDTH * 0.5,
});
// Perform the dragging action some arbitrarily large distance.
clickAndDrag(middleX, middleY, middleX - 10000, middleY);
expect(getChartViewport().viewportLeft).toBeGreaterThan(0.6);
expect(getChartViewport().viewportRight).toBe(1);
});
});
describe('keyboard navigation', function () {
it('zooms in and out at center', () => {
const { getChartViewport, depressKey } = setup();
// Zoom in.
depressKey('KeyQ', 500);
expect(getChartViewport().viewportLeft).toBeGreaterThan(0);
expect(getChartViewport().viewportRight).toBeLessThan(1);
// Assert we're centered.
expect(1 - getChartViewport().viewportRight).toBeCloseTo(
getChartViewport().viewportLeft,
7
);
// Remember where we are so that we can compare after zoom out.
const { viewportLeft, viewportRight } = getChartViewport();
// Zoom out.
depressKey('KeyE', 200);
expect(getChartViewport().viewportLeft).toBeLessThan(viewportLeft);
expect(getChartViewport().viewportRight).toBeGreaterThan(viewportRight);
// Assert we're still in the middle.
expect(1 - getChartViewport().viewportRight).toBeCloseTo(
getChartViewport().viewportLeft,
7
);
});
it('moves viewport down and up', () => {
const { getChartViewport, depressKey } = setup();
depressKey('KeyS', 100);
expect(getChartViewport()).toMatchObject({
viewportLeft: 0,
viewportRight: 1,
viewportTop: 200,
viewportBottom: BOUNDING_BOX_HEIGHT + 200,
});
depressKey('KeyW', 50);
expect(getChartViewport()).toMatchObject({
viewportLeft: 0,
viewportRight: 1,
viewportTop: 100,
viewportBottom: BOUNDING_BOX_HEIGHT + 100,
});
});
it('moves viewport left and right', () => {
const { getChartViewport, depressKey } = setup();
// Zoom in a bit to enable panning horizontally.
depressKey('KeyQ', 1000);
const { viewportLeft, viewportRight } = getChartViewport();
// Move to the left.
depressKey('KeyA', 100);
expect(getChartViewport().viewportLeft).toBeLessThan(viewportLeft);
expect(getChartViewport().viewportRight).toBeLessThan(viewportRight);
// Move to the right.
depressKey('KeyD', 200);
expect(getChartViewport().viewportLeft).toBeGreaterThan(viewportLeft);
expect(getChartViewport().viewportRight).toBeGreaterThan(viewportRight);
});
// eslint-disable-next-line jest/expect-expect
it('stops redrawing when losing focus', () => {
const { viewportContainer, flushRafCalls } = setup();
fireEvent.keyDown(viewportContainer(), { code: 'KeyQ' });
fireEvent.blur(viewportContainer());
fireEvent.keyUp(ensureExists(document.body), { code: 'KeyQ' });
// This will throw if we have an infinite loop.
flushRafCalls();
});
});
it('reacts to changes to the panel layout generation', function () {
const { dispatch, getChartViewport, flushRafCalls } = setup();
expect(getChartViewport()).toMatchObject({
containerWidth: BOUNDING_BOX_WIDTH,
containerHeight: BOUNDING_BOX_HEIGHT,
viewportLeft: 0,
viewportRight: 1,
viewportTop: 0,
viewportBottom: BOUNDING_BOX_HEIGHT,
});
const boundingWidthDiff = 15;
setMockedElementSize({
...INITIAL_ELEMENT_SIZE,
width: BOUNDING_BOX_WIDTH - boundingWidthDiff,
});
act(() => {
dispatch(changeSidebarOpenState('calltree', true));
});
flushRafCalls();
expect(getChartViewport()).toMatchObject({
containerWidth: BOUNDING_BOX_WIDTH - boundingWidthDiff,
containerHeight: BOUNDING_BOX_HEIGHT,
viewportLeft: 0,
viewportRight: 1,
viewportTop: 0,
viewportBottom: BOUNDING_BOX_HEIGHT,
});
});
});
function setup(profileOverrides: MixedObject = {}) {
const realFlushRafCalls = mockRaf();
const flushRafCalls = (...args) => act(() => realFlushRafCalls(...args));
// Hook up a dummy chart with a viewport.
const DummyChart = jest.fn(() => <div id="dummy-chart" />);
// Flow's internal structures for React don't recognize our mock function as a
// valid stateless component. $FlowExpectError
const ChartWithViewport = withChartViewport(DummyChart);
type Props = {
previewSelection: PreviewSelection,
timeRange: StartEndRange,
maxViewportHeight?: number,
startsAtBottom: boolean,
...
};
// The viewport component started out as an unconnected component, but then it
// started subscribing to the store an dispatching its own actions. This migration
// wasn't completely done, so it still has a few pieces of state passed in through
// its OwnProps. In order to ensure that the component is consistent, make it
// a connected component.
const ConnectedChartWithViewport = explicitConnect({
mapStateToProps: (state) => ({
timeRange: getCommittedRange(state),
previewSelection: getPreviewSelection(state),
}),
mapDispatchToProps: {},
component: (props: Props) => (
<ChartWithViewport
viewportProps={{
previewSelection: props.previewSelection,
timeRange: props.timeRange,
maxViewportHeight: props.maxViewportHeight || MAX_VIEWPORT_HEIGHT,
startsAtBottom: props.startsAtBottom,
viewportNeedsUpdate: () => false,
marginLeft: 0,
marginRight: 0,
maximumZoom: MAXIMUM_ZOOM,
...profileOverrides,
}}
chartProps={
{
// None used in dummy component.
}
}
/>
),
});
const store = storeWithProfile(getProfileFromTextSamples('A').profile);
const renderResult = render(
<Provider store={store}>
<ConnectedChartWithViewport />
</Provider>
);
const { container } = renderResult;
// WithSize uses requestAnimationFrame.
flushRafCalls();
// We rely on performance.now() for keyboard navigation.
jest.spyOn(performance, 'now').mockReturnValue(0);
// The following functions are helpers for the tests, to provide a nicer functional
// interface to drive changes to the components.
/**
* This helper function returns the last `viewport` prop passed to DummyChart.
*/
function getChartViewport() {
const calls = DummyChart.mock.calls;
return calls[calls.length - 1][0].viewport;
}
function viewportContainer() {
return ensureExists(
container.querySelector('.chartViewport'),
`Couldn't find the viewport container, with the selector .chartViewport`
);
}
function scroll(eventOverrides) {
fireEvent.wheel(viewportContainer(), eventOverrides);
flushRafCalls();
}
/**
* Send a keydown and keyup event with a specified duration in
* between.
*
* Assumes and relies on the fact that time is measured with
* `performance.now` and the timestamps sent to the
* requestAnimationFrame `_keyboardNavigation` callback.
*/
function depressKey(code: string, duration: Milliseconds) {
fireEvent.keyDown(viewportContainer(), { code });
// we run requestAnimationFrame callbacks only once here: indeed Viewport
// will schedule callbacks as long as the keyup event isn't sent, and we'd
// go into an infinite loop.
flushRafCalls({ timestamps: [duration], once: true });
fireEvent.keyUp(viewportContainer(), { code });
flushRafCalls();
}
function scrollAndGetViewport(eventOverrides) {
scroll(eventOverrides);
return getChartViewport();
}
function clickAndDrag(fromX, fromY, toX, toY) {
const from = { clientX: fromX, clientY: fromY };
const to = { clientX: toX, clientY: toY };
fireEvent.mouseDown(viewportContainer(), from);
fireEvent.mouseMove(viewportContainer(), from);
fireEvent.mouseMove(viewportContainer(), to);
fireEvent.mouseUp(viewportContainer(), to);
// Flush any batched updates.
flushRafCalls();
}
return {
...renderResult,
flushRafCalls,
getChartViewport,
viewportContainer,
scrollAndGetViewport,
scroll,
depressKey,
clickAndDrag,
dispatch: store.dispatch,
};
}