implement OverlayScrollbarsComponent for react

This commit is contained in:
Rene Haas
2022-10-26 18:59:55 +02:00
parent 7c5271e1ed
commit 3ecb32b349
3 changed files with 164 additions and 25 deletions
+6
View File
@@ -2,3 +2,9 @@ import matchers from '@testing-library/jest-dom/matchers';
import { expect } from 'vitest';
expect.extend(matchers);
// remove jsdom warning for not implemented second argument for window.getComputedStyle
try {
const cmptdStyle = window.getComputedStyle;
window.getComputedStyle = (a) => cmptdStyle(a);
} catch {}
@@ -1,24 +1,75 @@
export interface OverlayScrollbarsComponentProps {
element?: string;
options?: {};
events?: {};
import { forwardRef, useEffect, useRef, useImperativeHandle } from 'react';
import { OverlayScrollbars } from 'overlayscrollbars';
import type { PartialOptions, EventListeners } from 'overlayscrollbars';
import type { ComponentPropsWithoutRef, ElementRef, ForwardedRef } from 'react';
export type OverlayScrollbarsComponentProps<T extends keyof JSX.IntrinsicElements = 'div'> =
ComponentPropsWithoutRef<T> & {
element?: T;
options?: PartialOptions;
events?: EventListeners;
};
export interface OverlayScrollbarsComponentRef<T extends keyof JSX.IntrinsicElements = 'div'> {
osInstance(): OverlayScrollbars | null;
osTarget(): ElementRef<T> | null;
}
export const OverlayScrollbarsComponent = (props: OverlayScrollbarsComponentProps) => {
const { msg } = props;
return (
<div className="App">
<header className="App-header">
<p>{msg}</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
const OverlayScrollbarsComponent = <T extends keyof JSX.IntrinsicElements>(
props: OverlayScrollbarsComponentProps<T>,
ref: ForwardedRef<OverlayScrollbarsComponentRef<T>>
) => {
const { element = 'div', options, events, ...other } = props;
const Tag = element;
const osTargetRef = useRef<ElementRef<T>>(null);
const osInstanceRef = useRef<OverlayScrollbars | null>(null);
useEffect(() => {
const { current: target } = osTargetRef;
if (target) {
const instance = OverlayScrollbars(target as any, options || {});
osInstanceRef.current = instance;
return () => instance.destroy();
}
}, []);
useEffect(() => {
const { current: instance } = osInstanceRef;
if (OverlayScrollbars.valid(instance) && options) {
instance.options(options, true);
}
}, [options]);
useEffect(() => {
const { current: instance } = osInstanceRef;
if (OverlayScrollbars.valid(instance) && events) {
return instance.on(events);
}
}, [events]);
useImperativeHandle(
ref,
() => {
return {
osInstance: () => osInstanceRef.current,
osTarget: () => osTargetRef.current,
};
},
[]
);
// @ts-ignore
return <Tag data-overlayscrollbars="" {...other} ref={osTargetRef} />;
};
const OverlayScrollbarsComponentForwardedRef = forwardRef(OverlayScrollbarsComponent) as <
T extends keyof JSX.IntrinsicElements
>(
props: OverlayScrollbarsComponentProps<T> & {
ref?: ForwardedRef<OverlayScrollbarsComponentRef<T>>;
}
) => ReturnType<typeof OverlayScrollbarsComponent>;
export { OverlayScrollbarsComponentForwardedRef as OverlayScrollbarsComponent };
@@ -1,9 +1,91 @@
import { test, expect } from 'vitest';
import { describe, test, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { OverlayScrollbars } from 'overlayscrollbars';
import { OverlayScrollbarsComponent } from '~/overlayscrollbars-react';
import type { OverlayScrollbarsComponentRef } from '~/overlayscrollbars-react';
import type { RefObject } from 'react';
test('renders learn react link', () => {
render(<OverlayScrollbarsComponent msg="hi" />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
describe('OverlayScrollbarsComponent', () => {
describe('correct rendering', () => {
test('correct root element', () => {
const elementA = 'code';
const elementB = 'span';
const { container, rerender } = render(<OverlayScrollbarsComponent />);
expect(container).not.toBeEmptyDOMElement();
expect(container.querySelector('div')).toBe(container.firstElementChild); // default is div
rerender(<OverlayScrollbarsComponent element={elementA} />);
expect(container.querySelector(elementA)).toBe(container.firstElementChild);
rerender(<OverlayScrollbarsComponent element={elementB} />);
expect(container.querySelector(elementB)).toBe(container.firstElementChild);
});
test('children', () => {
render(
<OverlayScrollbarsComponent>
hello <span>react</span>
</OverlayScrollbarsComponent>
);
expect(screen.getByText(/hello/)).toBeInTheDocument();
expect(screen.getByText(/react/)).toBeInTheDocument();
});
test('className', () => {
const { container, rerender } = render(
<OverlayScrollbarsComponent className="overlay scrollbars" />
);
expect(container.firstElementChild).toHaveClass('overlay', 'scrollbars');
rerender(<OverlayScrollbarsComponent className="overlay scrollbars react" />);
expect(container.firstElementChild).toHaveClass('overlay', 'scrollbars', 'react');
});
test('style', () => {
const { container, rerender } = render(
<OverlayScrollbarsComponent style={{ width: '22px' }} />
);
expect(container.firstElementChild).toHaveStyle({ width: '22px' });
rerender(<OverlayScrollbarsComponent style={{ height: '33px' }} />);
expect(container.firstElementChild).toHaveStyle({ height: '33px' });
});
});
test('ref', () => {
const ref: RefObject<OverlayScrollbarsComponentRef> = { current: null };
const { container } = render(<OverlayScrollbarsComponent ref={ref} />);
const { osInstance, osTarget } = ref.current!;
expect(osInstance).toBeTypeOf('function');
expect(osTarget).toBeTypeOf('function');
expect(OverlayScrollbars.valid(osInstance())).toBe(true);
expect(osTarget()).toBe(container.firstElementChild);
});
test('options', () => {
const ref: RefObject<OverlayScrollbarsComponentRef> = { current: null };
const { rerender } = render(
<OverlayScrollbarsComponent
options={{ paddingAbsolute: true, overflow: { y: 'hidden' } }}
ref={ref}
/>
);
const opts = ref.current!.osInstance()!.options();
expect(opts.paddingAbsolute).toBe(true);
expect(opts.overflow.y).toBe('hidden');
rerender(<OverlayScrollbarsComponent options={{ overflow: { x: 'hidden' } }} ref={ref} />);
const newOpts = ref.current!.osInstance()!.options()!;
expect(newOpts.paddingAbsolute).toBe(false); //switches back to default because its not specified in the new options
expect(newOpts.overflow.y).toBe('scroll'); //switches back to default because its not specified in the new options
expect(newOpts.overflow.x).toBe('hidden');
});
});