Skip to content

Commit

Permalink
Add first component test with TimelineMarkers
Browse files Browse the repository at this point in the history
  • Loading branch information
gregtatum committed Jun 7, 2017
1 parent ba6bad2 commit e58b49f
Show file tree
Hide file tree
Showing 6 changed files with 373 additions and 30 deletions.
17 changes: 13 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,12 @@
"browserify": "^13.0.1",
"chai": "^3.5.0",
"css-loader": "^0.26.0",
"devtools-license-check": "^0.2.0",
"eslint": "^3.10.2",
"eslint-config-google": "^0.6.0",
"eslint-plugin-flowtype": "^2.30.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-react": "^6.4.0",
"devtools-license-check": "^0.2.0",
"fake-indexeddb": "^1.0.12",
"file-loader": "^0.9.0",
"flow-bin": "^0.40.0",
Expand All @@ -121,6 +121,7 @@
"lodash.clonedeep": "^4.5.0",
"mkdirp": "^0.5.1",
"raw-loader": "^0.5.1",
"react-test-renderer": "^15.5.4",
"rimraf": "^2.5.4",
"sinon": "^2.1.0",
"style-loader": "^0.13.1",
Expand All @@ -130,10 +131,18 @@
"webpack-hot-middleware": "^2.13.2"
},
"jest": {
"collectCoverageFrom" : ["src/**/*.{js,jsx}", "!**/node_modules/**"],
"collectCoverageFrom": [
"src/**/*.{js,jsx}",
"!**/node_modules/**"
],
"testEnvironment": "jsdom",
"moduleFileExtensions": ["js", "jsx"],
"moduleDirectories": ["node_modules"],
"moduleFileExtensions": [
"js",
"jsx"
],
"moduleDirectories": [
"node_modules"
],
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/src/test/fixtures/mocks/file-mock.js",
"\\.(css|less)$": "<rootDir>/src/test/fixtures/mocks/style-mock.js"
Expand Down
28 changes: 18 additions & 10 deletions src/content/components/TimelineCanvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ export default class TimelineCanvas<HoveredItem> extends Component<
_requestedAnimationFrame: boolean;
_devicePixelRatio: 1;
_ctx: CanvasRenderingContext2D;
_canvas: ?HTMLCanvasElement;

constructor(props: Props<HoveredItem>) {
super(props);
this._requestedAnimationFrame = false;
this._devicePixelRatio = 1;
this.state = { hoveredItem: null };

(this: any)._setCanvasRef = this._setCanvasRef.bind(this);
(this: any)._onMouseMove = this._onMouseMove.bind(this);
(this: any)._onMouseOut = this._onMouseOut.bind(this);
(this: any)._onDoubleClick = this._onDoubleClick.bind(this);
Expand All @@ -55,7 +57,7 @@ export default class TimelineCanvas<HoveredItem> extends Component<
this._requestedAnimationFrame = true;
window.requestAnimationFrame(() => {
this._requestedAnimationFrame = false;
if (this.refs.canvas) {
if (this._canvas) {
timeCode(`${className} render`, () => {
this._prepCanvas();
drawCanvas(this._ctx, this.state.hoveredItem);
Expand All @@ -66,39 +68,41 @@ export default class TimelineCanvas<HoveredItem> extends Component<
}

_prepCanvas() {
const {canvas} = this.refs;
const canvas = this._canvas;
const {containerWidth, containerHeight} = this.props;
const {devicePixelRatio} = window;
const pixelWidth: DevicePixels = containerWidth * devicePixelRatio;
const pixelHeight: DevicePixels = containerHeight * devicePixelRatio;
if (!canvas) {
return;
}
// Satisfy the null check for Flow.
const ctx = this._ctx || canvas.getContext('2d');
if (!this._ctx) {
this._ctx = canvas.getContext('2d');
this._ctx = ctx;
}
if (canvas.width !== pixelWidth || canvas.height !== pixelHeight) {
canvas.width = pixelWidth;
canvas.height = pixelHeight;
canvas.style.width = containerWidth + 'px';
canvas.style.height = containerHeight + 'px';
this._ctx.scale(this._devicePixelRatio, this._devicePixelRatio);
ctx.scale(this._devicePixelRatio, this._devicePixelRatio);
}
if (this._devicePixelRatio !== devicePixelRatio) {
// Make sure and multiply by the inverse of the previous ratio, as the scaling
// operates off of the previous set scale.
const scale = (1 / this._devicePixelRatio) * devicePixelRatio;
this._ctx.scale(scale, scale);
ctx.scale(scale, scale);
this._devicePixelRatio = devicePixelRatio;
}
return this._ctx;
}

_onMouseMove(event: SyntheticMouseEvent) {
const { canvas } = this.refs;
if (!canvas) {
if (!this._canvas) {
return;
}

const rect = canvas.getBoundingClientRect();
const rect = this._canvas.getBoundingClientRect();
const x: CssPixels = event.pageX - rect.left;
const y: CssPixels = event.pageY - rect.top;

Expand Down Expand Up @@ -126,6 +130,10 @@ export default class TimelineCanvas<HoveredItem> extends Component<
return this.props.getHoveredItemInfo(hoveredItem);
}

_setCanvasRef(canvas: HTMLCanvasElement) {
this._canvas = canvas;
}

render() {
const { hoveredItem } = this.state;
this._scheduleDraw();
Expand All @@ -137,7 +145,7 @@ export default class TimelineCanvas<HoveredItem> extends Component<
});

return <canvas className={className}
ref='canvas'
ref={this._setCanvasRef}
onMouseMove={this._onMouseMove}
onMouseOut={this._onMouseOut}
onDoubleClick={this._onDoubleClick}
Expand Down
40 changes: 24 additions & 16 deletions src/content/components/TimelineViewport.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import type {
import type { UpdateProfileSelection } from '../actions/profile-view';
import type { ProfileSelection } from '../actions/types';

const { DOM_DELTA_PAGE, DOM_DELTA_LINE } = new WheelEvent('mouse');
const { DOM_DELTA_PAGE, DOM_DELTA_LINE } = (typeof window === 'object' && window.WheelEvent)
? new WheelEvent('mouse')
: { DOM_DELTA_LINE: 1, DOM_DELTA_PAGE: 2 };

type Props = {
viewportNeedsUpdate: any,
Expand Down Expand Up @@ -68,10 +70,11 @@ const COLLAPSED_ROW_HEIGHT = 34;
export default function withTimelineViewport<T>(WrappedComponent: ReactClass<T>) {
class TimelineViewport extends PureComponent {

props: Props
shiftScrollId: number
zoomRangeSelectionScheduled: boolean
zoomRangeSelectionScrollDelta: number
props: Props;
shiftScrollId: number;
zoomRangeSelectionScheduled: boolean;
zoomRangeSelectionScrollDelta: number;
_container: ?HTMLElement;

state: {
containerWidth: CssPixels,
Expand Down Expand Up @@ -178,14 +181,16 @@ export default function withTimelineViewport<T>(WrappedComponent: ReactClass<T>)
}

_setSize() {
const rect = this.refs.container.getBoundingClientRect();
if (this.state.containerWidth !== rect.width || this.state.containerHeight !== rect.height) {
this.setState({
containerWidth: rect.width,
containerHeight: rect.height,
containerLeft: rect.left,
viewportBottom: this.state.viewportTop + rect.height,
});
if (this._container) {
const rect = this._container.getBoundingClientRect();
if (this.state.containerWidth !== rect.width || this.state.containerHeight !== rect.height) {
this.setState({
containerWidth: rect.width,
containerHeight: rect.height,
containerLeft: rect.left,
viewportBottom: this.state.viewportTop + rect.height,
});
}
}
}

Expand Down Expand Up @@ -224,13 +229,14 @@ export default function withTimelineViewport<T>(WrappedComponent: ReactClass<T>)

isViewportOccluded(event: SyntheticWheelEvent): boolean {
const scrollElement = this.props.getScrollElement();
if (!scrollElement) {
const container = this._container;
if (!scrollElement || !container) {
return false;
}
// Calculate using getBoundingClientRect to get non-rounded CssPixels.
const innerScrollRect = scrollElement.children[0].getBoundingClientRect();
const scrollRect = scrollElement.getBoundingClientRect();
const viewportRect = this.refs.container.getBoundingClientRect();
const viewportRect = container.getBoundingClientRect();

if (event.deltaY < 0) {
// ______________ viewportRect
Expand Down Expand Up @@ -469,7 +475,9 @@ export default function withTimelineViewport<T>(WrappedComponent: ReactClass<T>)
<div className={viewportClassName}
onWheel={this._mouseWheelListener}
onMouseDown={this._mouseDownListener}
ref='container'>
ref={container => {
this._container = container;
}}>
<WrappedComponent containerWidth={containerWidth}
containerHeight={containerHeight}
viewportLeft={viewportLeft}
Expand Down
80 changes: 80 additions & 0 deletions src/test/components/TimelineMarkers.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/* 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 React from 'react';
import TimelineMarkers from '../../content/containers/TimelineMarkers';
import renderer from 'react-test-renderer';
import { Provider } from 'react-redux';
import mockCanvasContext from '../fixtures/mocks/canvas-context';
import { storeWithProfile } from '../fixtures/stores';
import { getProfileWithMarkers } from '../store/fixtures/profiles';

jest.useFakeTimers();

it('renders TimelineMarkers correctly', () => {
// Tie the requestAnimationFrame into jest's fake timers.
window.requestAnimationFrame = fn => setTimeout(fn, 0);
window.devicePixelRatio = 1;
const ctx = mockCanvasContext();

/**
* Mock out any created refs for the components with relevant information.
*/
function createNodeMock(element) {
// <TimelineCanvas><canvas /></TimelineCanvas>
if (element.type === 'canvas') {
return {
getBoundingClientRect: () => _getBoundingBox(200, 300),
getContext: () => ctx,
style: {},
};
}
// <TimelineViewport />
if (element.props.className.split(' ').includes('timelineViewport')) {
return {
getBoundingClientRect: () => _getBoundingBox(200, 300),
};
}
return null;
}

const profile = getProfileWithMarkers([
['Marker A', 0, {startTime: 0, endTime: 10}],
['Marker B', 0, {startTime: 0, endTime: 10}],
['Marker C', 0, {startTime: 5, endTime: 15}],
]);

const timeline = renderer.create(
<Provider store={storeWithProfile(profile)}>
<TimelineMarkers threadIndex={0} viewHeight={1000} />
</Provider>,
{createNodeMock}
);

// Flush any requestAnimationFrames.
jest.runAllTimers();

const tree = timeline.toJSON();
const drawCalls = ctx.__flushDrawLog();

expect(tree).toMatchSnapshot();
expect(drawCalls).toMatchSnapshot();

delete window.requestAnimationFrame;
delete window.devicePixelRatio;
});

function _getBoundingBox(width, height) {
return {
width,
height,
left: 0,
x: 0,
top: 0,
y: 0,
right: width,
bottom: height,
};
}
Loading

0 comments on commit e58b49f

Please sign in to comment.