Skip to content

Commit

Permalink
too cool
Browse files Browse the repository at this point in the history
  • Loading branch information
oatkiller committed Feb 10, 2020
1 parent 0e0d3cf commit 30e7638
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@
*/

import { Middleware, Dispatch, MiddlewareAPI } from 'redux';
import { AppAction } from './action';
import { GlobalState } from '../types';
import { ResolverState, ResolverAction } from '../types';

export type Selector<S, R> = (state: S) => R;

export const substateMiddlewareFactory = <Substate>(
selector: Selector<GlobalState, Substate>,
middleware: Middleware<{}, Substate, Dispatch<AppAction>>
): Middleware<{}, GlobalState, Dispatch<AppAction>> => {
selector: Selector<ResolverState, Substate>,
middleware: Middleware<{}, Substate, Dispatch<ResolverAction>>
): Middleware<{}, ResolverState, Dispatch<ResolverAction>> => {
return api => {
const substateAPI: MiddlewareAPI<Dispatch<AppAction>, Substate> = {
const substateAPI: MiddlewareAPI<Dispatch<ResolverAction>, Substate> = {
...api,
getState() {
return selector(api.getState());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ export interface SideEffectors {
*/
timestamp: () => number;
requestAnimationFrame: typeof window.requestAnimationFrame;
cancelAnimationFrame: typeof window.cancelAnimationFrame;
ResizeObserver: ResizeObserverConstructor;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ import { SideEffectors } from '../types';

const sideEffectors: SideEffectors = {
timestamp: () => Date.now(),
requestAnimationFrame: window.requestAnimationFrame,
requestAnimationFrame(...args) {
return window.requestAnimationFrame(...args);
},
cancelAnimationFrame(...args) {
return window.cancelAnimationFrame(...args);
},
ResizeObserver,
};
export const SideEffectContext: Context<SideEffectors> = createContext(sideEffectors);
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import React from 'react';
import { render, act, RenderResult, fireEvent } from '@testing-library/react';
import { useCamera } from './use_camera';
import { Provider } from 'react-redux';
import * as selectors from '../store/selectors';
import { storeFactory } from '../store';
import {
Matrix3,
SideEffectors,
ResolverMiddleware,
ResolverAction,
ResolverStore,
ProcessEvent,
} from '../types';
import { MockResizeObserver } from './mock_resize_observer';
import { SideEffectContext } from './side_effect_context';
Expand All @@ -29,11 +31,14 @@ describe('useCamera on an unpainted element', () => {
let time: number;
let frameRequestedCallbacksIDCounter: number;
let frameRequestedCallbacks: Map<number, FrameRequestCallback>;
let provideAnimationFrame: () => void;
const testID = 'camera';
let reactRenderQueries: RenderResult;
let reactRenderResult: RenderResult;
let simulateElementResize: (target: Element, contentRect: DOMRect) => void;
let actions: ResolverAction[];
let store: ResolverStore;
let sideEffectors: jest.Mocked<Omit<SideEffectors, 'ResizeObserver'>> &
Pick<SideEffectors, 'ResizeObserver'>;
beforeEach(async () => {
actions = [];
const middleware: ResolverMiddleware = () => next => action => {
Expand All @@ -52,26 +57,40 @@ describe('useCamera on an unpainted element', () => {
time = 0;
frameRequestedCallbacksIDCounter = 0;
frameRequestedCallbacks = new Map();
provideAnimationFrame = () => {
// TODO should we 'act'?
act(() => {
/**
* Iterate the values, and clear the data set before calling the callbacks because the callbacks will repopulate the dataset synchronously in this testing framework.
*/
const values = [...frameRequestedCallbacks.values()];
frameRequestedCallbacks.clear();
for (const callback of values) {
callback(time);
}
});
};

jest
.spyOn(Element.prototype, 'getBoundingClientRect')
.mockImplementation(function(this: Element) {
return MockResizeObserver.contentRectForElement(this);
});

const sideEffectors: SideEffectors = {
timestamp: jest.fn().mockImplementation(),
requestAnimationFrame: jest
.fn()
.mockImplementation((callback: FrameRequestCallback): number => {
const id = frameRequestedCallbacksIDCounter++;
frameRequestedCallbacks.set(id, callback);
return id;
}),
sideEffectors = {
timestamp: jest.fn(() => time),
requestAnimationFrame: jest.fn((callback: FrameRequestCallback): number => {
const id = frameRequestedCallbacksIDCounter++;
frameRequestedCallbacks.set(id, callback);
return id;
}),
cancelAnimationFrame: jest.fn((id: number) => {
frameRequestedCallbacks.delete(id);
}),
ResizeObserver: MockResizeObserver,
};

reactRenderQueries = render(
reactRenderResult = render(
<Provider store={store}>
<SideEffectContext.Provider value={sideEffectors}>
<Test />
Expand All @@ -80,7 +99,7 @@ describe('useCamera on an unpainted element', () => {
);

simulateElementResize = MockResizeObserver.simulateElementResize;
const { findByTestId } = reactRenderQueries;
const { findByTestId } = reactRenderResult;
element = await findByTestId(testID);
});
afterEach(() => {
Expand All @@ -90,21 +109,9 @@ describe('useCamera on an unpainted element', () => {
expect(element).toBeInTheDocument();
});
test('returns a projectionMatrix that changes everything to 0', () => {
expect(projectionMatrix).toMatchInlineSnapshot(`
Array [
0,
0,
0,
0,
0,
0,
0,
0,
0,
]
`);
expect(applyMatrix3([0, 0], projectionMatrix)).toEqual([0, 0]);
});
describe('which has been resize to 800x400', () => {
describe('which has been resized to 800x600', () => {
const width = 800;
const height = 600;
const leftMargin = 20;
Expand All @@ -128,20 +135,8 @@ describe('useCamera on an unpainted element', () => {
});
});
});
test('provides a projection matrix', () => {
expect(projectionMatrix).toMatchInlineSnapshot(`
Array [
1,
0,
400,
0,
-1,
300,
0,
0,
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]);
});
describe('when the user presses the mousedown button in the middle of the element', () => {
beforeEach(() => {
Expand All @@ -162,5 +157,76 @@ describe('useCamera on an unpainted element', () => {
});
});
});

describe('when the user uses the mousewheel w/ ctrl held down', () => {
beforeEach(() => {
fireEvent.wheel(element, {
ctrlKey: true,
deltaY: -10,
deltaMode: 0,
});
});
it('should zoom in', () => {
expect(projectionMatrix).toMatchInlineSnapshot(`
Array [
1.0635255481707058,
0,
400,
0,
-1.0635255481707058,
300,
0,
0,
0,
]
`);
});
});

// TODO, move to new module
it('should not initially request an animation frame', () => {
expect(sideEffectors.requestAnimationFrame).not.toHaveBeenCalled();
});
describe('when the camera begins animation', () => {
const initialTime = 0;
let process: ProcessEvent;
beforeEach(() => {
/**
* At this time, processes are provided via mock data. In the future, this test will have to provide those mocks.
*/
const processes: ProcessEvent[] = [
...selectors
.processNodePositionsAndEdgeLineSegments(store.getState())
.processNodePositions.keys(),
];
process = processes[processes.length - 1];
const action: ResolverAction = {
type: 'userBroughtProcessIntoView',
payload: {
time: initialTime,
process,
},
};
// does this need to be in act? prolly
act(() => {
store.dispatch(action);
});
});

it('should request animation frames in a loop', () => {
expect(sideEffectors.requestAnimationFrame).toHaveBeenCalledTimes(1);
time = 100;
provideAnimationFrame();
expect(sideEffectors.requestAnimationFrame).toHaveBeenCalledTimes(2);
time = 900;
provideAnimationFrame();
expect(sideEffectors.requestAnimationFrame).toHaveBeenCalledTimes(3);
// Animation lasts 1000ms, so this should end it
time = 1001;
provideAnimationFrame();
// Doesn't ask again, still 3
expect(sideEffectors.requestAnimationFrame).toHaveBeenCalledTimes(3);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -210,15 +210,15 @@ export function useCamera(): {
setProjectionMatrix(projectionMatrixAtTimeRef.current(date));
}
if (isAnimatingAtTime(date)) {
rafRef = requestAnimationFrame(handleFrame);
rafRef = sideEffectors.requestAnimationFrame(handleFrame);
} else {
rafRef = null;
}
};
rafRef = requestAnimationFrame(handleFrame);
rafRef = sideEffectors.requestAnimationFrame(handleFrame);
return () => {
if (rafRef !== null) {
cancelAnimationFrame(rafRef);
sideEffectors.cancelAnimationFrame(rafRef);
}
};
}
Expand Down

0 comments on commit 30e7638

Please sign in to comment.