From 72fe4d6325a19f6505afc80dc0b373e737a9787c Mon Sep 17 00:00:00 2001 From: CipherGirl Date: Mon, 26 Oct 2020 21:18:17 +0600 Subject: [PATCH 1/2] Update hoverLocation to use mouseTimePosition redux state --- src/components/timeline/Selection.js | 46 ++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/src/components/timeline/Selection.js b/src/components/timeline/Selection.js index e04a815660..44c447f836 100644 --- a/src/components/timeline/Selection.js +++ b/src/components/timeline/Selection.js @@ -11,10 +11,12 @@ import { getPreviewSelection, getCommittedRange, getZeroAt, + getMouseTimePosition, } from 'firefox-profiler/selectors/profile'; import { updatePreviewSelection, commitRange, + changeMouseTimePosition, } from 'firefox-profiler/actions/profile-view'; import explicitConnect from 'firefox-profiler/utils/connect'; import classNames from 'classnames'; @@ -43,20 +45,18 @@ type StateProps = {| +previewSelection: PreviewSelection, +committedRange: StartEndRange, +zeroAt: Milliseconds, + +mouseTimePosition: Milliseconds | null, |}; type DispatchProps = {| +commitRange: typeof commitRange, +updatePreviewSelection: typeof updatePreviewSelection, + +changeMouseTimePosition: typeof changeMouseTimePosition, |}; type Props = ConnectedProps; -type State = {| - hoverLocation: null | CssPixels, -|}; - -class TimelineRulerAndSelection extends React.PureComponent { +class TimelineRulerAndSelection extends React.PureComponent { _handlers: ?{| mouseMoveHandler: MouseHandler, mouseClickHandler: MouseHandler, @@ -64,10 +64,6 @@ class TimelineRulerAndSelection extends React.PureComponent { _container: ?HTMLElement; - state = { - hoverLocation: null, - }; - _containerCreated = (element: HTMLElement | null) => { this._container = element; }; @@ -246,6 +242,7 @@ class TimelineRulerAndSelection extends React.PureComponent { if (!this._container) { return; } + const { width, committedRange, changeMouseTimePosition } = this.props; const rect = getContentRect(this._container); if ( @@ -254,9 +251,15 @@ class TimelineRulerAndSelection extends React.PureComponent { event.pageY < rect.top || event.pageY >= rect.bottom ) { - this.setState({ hoverLocation: null }); + changeMouseTimePosition(null); } else { - this.setState({ hoverLocation: event.pageX - rect.left }); + const hoverPositionInPixels = event.pageX - rect.left; + const pixelsToMouseTimePosition = Math.round( + ((committedRange.end - committedRange.start) * hoverPositionInPixels) / + width + + committedRange.start + ); + changeMouseTimePosition(pixelsToMouseTimePosition); } }; @@ -386,8 +389,23 @@ class TimelineRulerAndSelection extends React.PureComponent { } render() { - const { children, previewSelection, className } = this.props; - const { hoverLocation } = this.state; + const { + children, + previewSelection, + className, + mouseTimePosition, + width, + committedRange, + } = this.props; + + let hoverLocation = null; + + if (mouseTimePosition !== null) { + // If the mouseTimePosition exists, convert it to CssPixels. + hoverLocation = + (width * (mouseTimePosition - committedRange.start)) / + (committedRange.end - committedRange.start); + } return (
Date: Tue, 20 Dec 2022 18:59:07 +0100 Subject: [PATCH 2/2] Tests for using mouseTimePosition to draw hoverline in timeline area --- src/test/components/Timeline.test.js | 131 ++++++- .../__snapshots__/Timeline.test.js.snap | 331 ++++++++++++++++++ src/test/fixtures/mocks/domrect.js | 2 +- 3 files changed, 450 insertions(+), 14 deletions(-) create mode 100644 src/test/components/__snapshots__/Timeline.test.js.snap diff --git a/src/test/components/Timeline.test.js b/src/test/components/Timeline.test.js index ae8f002e63..6021b17d88 100644 --- a/src/test/components/Timeline.test.js +++ b/src/test/components/Timeline.test.js @@ -6,13 +6,19 @@ import * as React from 'react'; import { Provider } from 'react-redux'; -import { render, screen } from 'firefox-profiler/test/fixtures/testing-library'; +import { + render, + fireEvent, + screen, +} from 'firefox-profiler/test/fixtures/testing-library'; import { Timeline } from '../../components/timeline'; import { selectedThreadSelectors, getRightClickedTrack, + getMouseTimePosition, } from 'firefox-profiler/selectors'; -import { ensureExists } from '../../utils/flow'; +import { FULL_TRACK_SCREENSHOT_HEIGHT } from 'firefox-profiler/app-logic/constants'; +import { ensureExists } from 'firefox-profiler/utils/flow'; import { storeWithProfile } from '../fixtures/stores'; import { @@ -27,6 +33,7 @@ import { getElementWithFixedSize, } from '../fixtures/mocks/element-size'; import { + getMouseEvent, fireFullClick, fireFullKeyPress, fireFullContextMenu, @@ -41,12 +48,24 @@ import { autoMockIntersectionObserver } from '../fixtures/mocks/intersection-obs import type { Profile, ThreadIndex } from 'firefox-profiler/types'; -describe('Timeline multiple thread selection', function () { - autoMockDomRect(); - autoMockCanvasContext(); - autoMockElementSize({ width: 200, height: 300 }); - autoMockIntersectionObserver(); +// Mock out the element size to have a 400 pixel width and some left/top +// positioning. +const TRACK_WIDTH = 400; +const LEFT = 100; +const TOP = 7; +const INITIAL_ELEMENT_SIZE = { + width: TRACK_WIDTH, + height: FULL_TRACK_SCREENSHOT_HEIGHT, + offsetX: LEFT, + offsetY: TOP, +}; + +autoMockDomRect(); +autoMockCanvasContext(); +autoMockElementSize(INITIAL_ELEMENT_SIZE); +autoMockIntersectionObserver(); +describe('Timeline multiple thread selection', function () { function setup(profile = getProfileWithNiceTracks()) { const store = storeWithProfile(profile); @@ -169,7 +188,10 @@ describe('Timeline multiple thread selection', function () { null ); - fireFullClick(activityGraph, { offsetX: 50, offsetY: 50 }); + fireFullClick(activityGraph, { + offsetX: TRACK_WIDTH / 2, + offsetY: (FULL_TRACK_SCREENSHOT_HEIGHT * 3) / 4, + }); expect(getHumanReadableTracks(getState())).toEqual([ 'show [thread GeckoMain default]', @@ -1192,11 +1214,6 @@ function _getProfileWithDroppedSamples(): Profile { } describe('Timeline', function () { - autoMockCanvasContext(); - autoMockElementSize({ width: 200, height: 300 }); - autoMockIntersectionObserver(); - autoMockDomRect(); - beforeEach(() => { jest .spyOn(ReactDOM, 'findDOMNode') @@ -1358,3 +1375,91 @@ describe('Timeline', function () { }); }); }); + +describe('TimelineSelection', () => { + function setup() { + const flushRafCalls = mockRaf(); + const profileLength = 10; + // There are 10 samples in this profile. + const { profile } = getProfileFromTextSamples('A '.repeat(profileLength)); + + // getBoundingClientRect is already mocked by autoMockElementSize. + jest + .spyOn(HTMLElement.prototype, 'getClientRects') + .mockImplementation(() => { + const result = [ + new DOMRect(LEFT, TOP, TRACK_WIDTH, FULL_TRACK_SCREENSHOT_HEIGHT), + ]; + return result; + }); + + const store = storeWithProfile(profile); + render( + + + + ); + + // This is necessary to make sure the sizing is correct. + flushRafCalls(); + + function moveMouseOnThreadCanvas(mouseEventOptions) { + const threadCanvas = ensureExists( + document.querySelector('.threadActivityGraphCanvas'), + 'Expected that a thread activity graph canvas is present.' + ); + + fireEvent(threadCanvas, getMouseEvent('mousemove', mouseEventOptions)); + } + + function getTimePositionLinePosition() { + const positionLine = ensureExists( + document.querySelector('.timelineSelectionHoverLine'), + 'Expected that the vertical line indicating the time position is present.' + ); + return parseInt(positionLine.style.left); + } + + return { + ...store, + profileLength, + flushRafCalls, + moveMouseOnThreadCanvas, + getTimePositionLinePosition, + }; + } + + it('renders the vertical line indicating the time position from the mouse cursor', () => { + const { + moveMouseOnThreadCanvas, + getState, + profileLength, + getTimePositionLinePosition, + } = setup(); + let samplePosition = 3; + + moveMouseOnThreadCanvas({ + pageX: LEFT + (TRACK_WIDTH * samplePosition) / profileLength, + pageY: TOP + 1, + }); + + expect(getMouseTimePosition(getState())).toBe(samplePosition); + expect(getTimePositionLinePosition()).toBe( + (TRACK_WIDTH * samplePosition) / profileLength + ); + expect(document.body).toMatchSnapshot(); + + // Move the mouse in another position. + samplePosition = 6; + + moveMouseOnThreadCanvas({ + pageX: LEFT + (TRACK_WIDTH * samplePosition) / profileLength, + pageY: TOP + 1, + }); + + expect(getMouseTimePosition(getState())).toBe(samplePosition); + expect(getTimePositionLinePosition()).toBe( + (TRACK_WIDTH * samplePosition) / profileLength + ); + }); +}); diff --git a/src/test/components/__snapshots__/Timeline.test.js.snap b/src/test/components/__snapshots__/Timeline.test.js.snap new file mode 100644 index 0000000000..77465d5312 --- /dev/null +++ b/src/test/components/__snapshots__/Timeline.test.js.snap @@ -0,0 +1,331 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TimelineSelection renders the vertical line indicating the time position from the mouse cursor 1`] = ` + +
+
+
+ +
+
    +
  1. + + 0.000s + +
  2. +
  3. + + 0.002s + +
  4. +
  5. + + 0.004s + +
  6. +
  7. + + 0.006s + +
  8. +
  9. + + 0.008s + +
  10. +
  11. + + 0.010s + +
  12. +
+
+
+
+
+
+
+
+
+
+
    +
  1. +
    +
    + +
    +
    +
    +
    +
    +
      +
    1. +
      +
      + +
      +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      + +

      + Activity Graph for + Empty +

      +

      + This graph shows a visual chart of thread activity. +

      +
      +
      +
      +
      +
      + +

      + Stack Graph for + Empty +

      +

      + This graph charts the stack height of each sample. +

      +
      +
      +
      +
      +
      +
      +
      +
    2. +
    +
  2. +
+
+
+
+
+
+ +
+ +`; diff --git a/src/test/fixtures/mocks/domrect.js b/src/test/fixtures/mocks/domrect.js index 6bd930ed82..1cee7af459 100644 --- a/src/test/fixtures/mocks/domrect.js +++ b/src/test/fixtures/mocks/domrect.js @@ -16,7 +16,7 @@ class DOMRect { height: number; constructor(x: number, y: number, width: number, height: number) { - this.x = y; + this.x = x; this.y = y; this.width = width; this.height = height;