Navigation Menu

Skip to content

Commit

Permalink
Panning resize fix (elastic#65924)
Browse files Browse the repository at this point in the history
* prevent animation when target is same as initial

* fixed resolver resize trigger

* explicitly set animation to undefined

* updated tests for resize reference

* make resize ref test a bit more explicit
  • Loading branch information
michaelolo24 committed May 13, 2020
1 parent 7707cbd commit cba2a65
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 11 deletions.
Expand Up @@ -49,12 +49,56 @@ describe('when the camera is created', () => {
};
store = createStore(testReducer);
});

it('should be at 0,0', () => {
expect(selectors.translation(store.getState())(0)).toEqual([0, 0]);
});
it('should have scale of [1,1]', () => {
expect(selectors.scale(store.getState())(0)).toEqual([1, 1]);
});

describe('When attempting to pan to current position and scale', () => {
const duration = 1000;
const startTime = 0;
beforeEach(() => {
const action: TestAction = {
type: 'animatePanning',
payload: {
time: startTime,
duration,
targetTranslation: [0, 0],
},
};
store.dispatch(action);
});

describe('when the animation is in progress', () => {
let translationAtIntervals: Vector2[];
let scaleAtIntervals: Vector2[];
beforeEach(() => {
translationAtIntervals = [];
scaleAtIntervals = [];
const state = store.getState();
for (let progress = 0; progress <= 1; progress += 0.1) {
translationAtIntervals.push(
selectors.translation(state)(lerp(startTime, startTime + duration, progress))
);
scaleAtIntervals.push(
selectors.scale(state)(lerp(startTime, startTime + duration, progress))
);
}
});

it('should not translate', () => {
expect(translationAtIntervals.every(([x, y]: Vector2) => x === 0 && y === 0)).toBe(true);
});

it('should not scale', () => {
expect(scaleAtIntervals.every(([x, y]: Vector2) => x === 1 && y === 1)).toBe(true);
});
});
});

describe('when animation begins', () => {
const duration = 1000;
let targetTranslation: Vector2;
Expand Down
Expand Up @@ -6,6 +6,7 @@

import { translation } from './selectors';
import { CameraState, Vector2 } from '../../types';
import { distance } from '../../lib/vector2';

/**
* Return a new `CameraState` with the `animation` property
Expand All @@ -17,6 +18,17 @@ export function animatePanning(
targetTranslation: Vector2,
duration: number
): CameraState {
const initialTranslation = translation(state)(startTime);
const translationDistance = distance(targetTranslation, initialTranslation);

if (translationDistance === 0) {
return {
...state,
animation: undefined,
panning: undefined,
};
}

const nextState: CameraState = {
...state,
/**
Expand All @@ -27,7 +39,7 @@ export function animatePanning(
animation: {
startTime,
targetTranslation,
initialTranslation: translation(state)(startTime),
initialTranslation,
duration,
},
};
Expand Down
Expand Up @@ -4,9 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import React, { FunctionComponent } from 'react';
import { render, act, RenderResult, fireEvent } from '@testing-library/react';
import { useCamera } from './use_camera';
import { renderHook, act as hooksAct } from '@testing-library/react-hooks';
import { useCamera, useAutoUpdatingClientRect } from './use_camera';
import { Provider } from 'react-redux';
import * as selectors from '../store/selectors';
import { storeFactory } from '../store';
Expand Down Expand Up @@ -81,6 +82,28 @@ describe('useCamera on an unpainted element', () => {
});
});
});
test('should observe all resize reference changes', async () => {
const wrapper: FunctionComponent = ({ children }) => (
<Provider store={store}>
<SideEffectContext.Provider value={simulator.mock}>{children}</SideEffectContext.Provider>
</Provider>
);

const { result } = renderHook(() => useAutoUpdatingClientRect(), { wrapper });
const resizeObserverSpy = jest.spyOn(simulator.mock.ResizeObserver.prototype, 'observe');

let [rect, ref] = result.current;
hooksAct(() => ref(element));
expect(resizeObserverSpy).toHaveBeenCalledWith(element);

const div = document.createElement('div');
hooksAct(() => ref(div));
expect(resizeObserverSpy).toHaveBeenCalledWith(div);

[rect, ref] = result.current;
expect(rect?.width).toBe(0);
});

test('provides a projection matrix that inverts the y axis and translates 400,300 (center of the element)', () => {
expect(applyMatrix3([0, 0], projectionMatrix)).toEqual([400, 300]);
});
Expand Down
Expand Up @@ -280,28 +280,30 @@ export function useCamera(): {
* tracked. So if the element's position moves for some reason, be sure to
* handle that.
*/
function useAutoUpdatingClientRect(): [DOMRect | null, (node: Element | null) => void] {
export function useAutoUpdatingClientRect(): [DOMRect | null, (node: Element | null) => void] {
const [rect, setRect] = useState<DOMRect | null>(null);
const nodeRef = useRef<Element | null>(null);
// Using state as ref.current update does not trigger effect hook when reset
const [currentNode, setCurrentNode] = useState<Element | null>(null);

const ref = useCallback((node: Element | null) => {
nodeRef.current = node;
setCurrentNode(node);
if (node !== null) {
setRect(node.getBoundingClientRect());
}
}, []);
const { ResizeObserver } = useContext(SideEffectContext);
useEffect(() => {
if (nodeRef.current !== null) {
if (currentNode !== null) {
const resizeObserver = new ResizeObserver(entries => {
if (nodeRef.current !== null && nodeRef.current === entries[0].target) {
setRect(nodeRef.current.getBoundingClientRect());
if (currentNode !== null && currentNode === entries[0].target) {
setRect(currentNode.getBoundingClientRect());
}
});
resizeObserver.observe(nodeRef.current);
resizeObserver.observe(currentNode);
return () => {
resizeObserver.disconnect();
};
}
}, [ResizeObserver, nodeRef]);
}, [ResizeObserver, currentNode]);
return [rect, ref];
}

0 comments on commit cba2a65

Please sign in to comment.