Skip to content

Commit

Permalink
fix(hooks): allow updates to options at runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
garthenweb committed Sep 8, 2019
1 parent 94b6c31 commit 3f7bfa2
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 8 deletions.
127 changes: 127 additions & 0 deletions lib/__tests__/hooks.client.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// force fallback to setTimeout
delete window.requestAnimationFrame;
jest.useFakeTimers();

import React, { useState } from 'react';
import { act } from 'react-dom/test-utils';
import { render, cleanup, fireEvent } from '@testing-library/react';
import { ViewportProvider, useViewport } from '../index';

const scrollTo = (x: number, y: number) => {
window.scrollTo(x, y);
act(() => {
jest.advanceTimersByTime(20);
});
};

describe('hooks', () => {
beforeEach(() => {
const eventMap: any = {
scroll: jest.fn(),
};
jest.spyOn(window, 'addEventListener').mockImplementation((event, cb) => {
eventMap[event] = cb;
});

jest
.spyOn(window, 'scrollTo')
.mockImplementation((x: number, y: number) => {
(window as any).scrollX = x;
(window as any).scrollY = y;
eventMap.scroll && eventMap.scroll();
});
});

afterEach(() => {
cleanup();
jest.clearAllTimers();
});

describe('useViewport', () => {
it('should update on viewport change', async () => {
const App = () => {
const viewport = useViewport();
return (
<div>
scroll: {viewport.scroll.x},{viewport.scroll.y}
</div>
);
};
const { getByText } = render(
<ViewportProvider>
<App />
</ViewportProvider>,
);
act(() => {
jest.advanceTimersByTime(20);
});
scrollTo(0, 1000);
expect(getByText('scroll: 0,1000')).toBeDefined();

scrollTo(0, 2000);
expect(getByText('scroll: 0,2000')).toBeDefined();
});

it('should not update if disabled', async () => {
const App = () => {
const viewport = useViewport({
disableScrollUpdates: true,
});
return (
<div>
scroll: {viewport.scroll.x},{viewport.scroll.y}
</div>
);
};
const { rerender, getByText } = render(<ViewportProvider />);
scrollTo(0, 1000);
rerender(
<ViewportProvider>
<App />
</ViewportProvider>,
);
act(() => {
jest.advanceTimersByTime(20);
});
expect(getByText('scroll: 0,1000')).toBeDefined();
scrollTo(0, 2000);
expect(getByText('scroll: 0,1000')).toBeDefined();
});

it('should not update if disabled at runtime', async () => {
const App = () => {
const [disableScrollUpdates, setDisableScrollUpdate] = useState(false);
const viewport = useViewport({
disableScrollUpdates,
});
return (
<div onClick={() => setDisableScrollUpdate(!disableScrollUpdates)}>
scroll: {viewport.scroll.x},{viewport.scroll.y}
</div>
);
};
const { rerender, getByText } = render(<ViewportProvider />);
scrollTo(0, 1000);
rerender(
<ViewportProvider>
<App />
</ViewportProvider>,
);
act(() => {
jest.advanceTimersByTime(20);
});
expect(getByText('scroll: 0,1000')).toBeDefined();

// disable
fireEvent.click(getByText('scroll: 0,1000'));
scrollTo(0, 2000);
expect(getByText('scroll: 0,1000')).toBeDefined();

// enable
fireEvent.click(getByText('scroll: 0,1000'));

scrollTo(0, 3000);
expect(getByText('scroll: 0,3000')).toBeDefined();
});
});
});
24 changes: 18 additions & 6 deletions lib/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useContext, useEffect, useState, RefObject } from 'react';
import { useContext, useEffect, useState, RefObject, useRef } from 'react';

import { ViewportContext } from './ViewportProvider';
import { IViewport, IScroll, IDimensions, PriorityType, IRect } from './types';
Expand All @@ -14,13 +14,24 @@ interface IFullOptions extends IOptions {
}

interface IOptions {
[key: string]: unknown;
deferUpdateUntilIdle?: boolean;
priority?: PriorityType;
}
interface IEffectOptions<T> extends IOptions {
recalculateLayoutBeforeUpdate?: (viewport: IViewport) => T;
}

const useOptions = <T>(o: IViewPortEffectOptions<T>) => {
const optionsRef = useRef<IViewPortEffectOptions<T>>(Object.create(null));
for (const key of Object.keys(optionsRef.current)) {
delete optionsRef.current[key];
}
Object.assign(optionsRef.current, o);

return optionsRef.current;
};

export const useViewportEffect = <T>(
handleViewportChange: (viewport: IViewport, snapshot: T) => void,
options: IViewPortEffectOptions<T> = {},
Expand All @@ -30,18 +41,19 @@ export const useViewportEffect = <T>(
removeViewportChangeListener,
hasRootProviderAsParent,
} = useContext(ViewportContext);
const memoOptions = useOptions(options);

useEffect(() => {
if (!hasRootProviderAsParent) {
warnNoContextAvailable('useViewport');
return;
}
addViewportChangeListener(handleViewportChange, {
notifyScroll: () => !options.disableScrollUpdates,
notifyDimensions: () => !options.disableDimensionsUpdates,
notifyOnlyWhenIdle: () => Boolean(options.deferUpdateUntilIdle),
priority: () => options.priority || 'normal',
recalculateLayoutBeforeUpdate: options.recalculateLayoutBeforeUpdate,
notifyScroll: () => !memoOptions.disableScrollUpdates,
notifyDimensions: () => !memoOptions.disableDimensionsUpdates,
notifyOnlyWhenIdle: () => Boolean(memoOptions.deferUpdateUntilIdle),
priority: () => memoOptions.priority || 'normal',
recalculateLayoutBeforeUpdate: memoOptions.recalculateLayoutBeforeUpdate,
});
return () => removeViewportChangeListener(handleViewportChange);
}, [addViewportChangeListener || null, removeViewportChangeListener || null]);
Expand Down
2 changes: 0 additions & 2 deletions lib/modules.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,3 @@ interface DOMRectReadOnly {

toJSON: () => any;
}

declare module '@testing-library/react';

0 comments on commit 3f7bfa2

Please sign in to comment.