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 (
{ 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;