Skip to content

Commit

Permalink
refactor: items with initial zero bounds changing their bounds after …
Browse files Browse the repository at this point in the history
…mount
  • Loading branch information
nerdyman committed Oct 28, 2023
1 parent 0fd892a commit 646d88e
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 16 deletions.
101 changes: 95 additions & 6 deletions docs/storybook/content/stories/99-tests/zero-bounds.test.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { expect } from '@storybook/jest';
import type { Meta } from '@storybook/react';
import { waitFor, within } from '@storybook/testing-library';
import type { ReactCompareSlider } from 'react-compare-slider';
import { userEvent, waitFor, within } from '@storybook/testing-library';
import { useState } from 'react';
import type { ReactCompareSliderDetailedProps } from 'react-compare-slider';
import { ReactCompareSlider } from 'react-compare-slider';

import { Template, getArgs } from './test-utils.test';

Expand All @@ -20,11 +22,98 @@ ZeroBounds.args = getArgs({

ZeroBounds.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const sliderRoot = canvas.queryByTestId(ZeroBounds.args?.['data-testid']) as Element;
const slider = canvas.getByRole('slider') as Element;

// Should have elements on mount and not crash.
await new Promise((resolve) => setTimeout(resolve, 500));
await waitFor(() => expect(sliderRoot).toBeInTheDocument());
await waitFor(() => expect(slider).toBeInTheDocument());
await waitFor(() => expect(canvas.getByTestId('one')).toBeInTheDocument());
await waitFor(() => expect(canvas.getByTestId('two')).toBeInTheDocument());
await waitFor(() => expect(slider.getAttribute('aria-valuenow')).toBe('50'));
};

/**
* Rendering items with no width or height the change them to images after rendering.
*/
export const ZeroBoundsWithLazyContent = () => {
const [dir, setDir] = useState('ltr');
const [props, setProps] = useState<ReactCompareSliderDetailedProps>({
position: 75,
portrait: true,
itemOne: <div data-testid="one" />,
itemTwo: <div data-testid="two" />,
});

const loadContent = async () => {
await new Promise((resolve) => setTimeout(resolve, 1500));
setProps((prev) => ({
...prev,
itemOne: (
<img
data-testid="one"
alt="one"
src="https://raw.githubusercontent.com/nerdyman/stuff/main/libs/react-compare-slider/demo-images/e2e-test-1.png"
style={{ width: 128, height: 128, objectFit: 'cover' }}
/>
),
itemTwo: (
<img
data-testid="two"
alt="two"
src="https://raw.githubusercontent.com/nerdyman/stuff/main/libs/react-compare-slider/demo-images/e2e-test-2.png"
style={{ width: 128, height: 128, objectFit: 'cover' }}
/>
),
}));
};

return (
<div dir={dir} style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
<div style={{ display: 'flex', gap: 8 }}>
<button onClick={loadContent}>Load images</button>
<button onClick={() => setProps((prev) => ({ ...prev, portrait: !prev.portrait }))}>
Toggle portrait
</button>
<button onClick={() => setDir((prev) => (prev === 'ltr' ? 'rtl' : 'ltr'))}>
Toggle direction
</button>
</div>
<ReactCompareSlider {...props} />
</div>
);
};

ZeroBoundsWithLazyContent.play = async ({ canvasElement }) => {
const user = userEvent.setup();
const canvas = within(canvasElement);
const slider = (await canvas.findByRole('slider')) as Element;
const loadImages = await canvas.findByText('Load images');
const togglePortrait = await canvas.findByText('Toggle portrait');
const toggleDirection = await canvas.findByText('Toggle direction');

await waitFor(() => expect(slider).toBeInTheDocument());
await waitFor(() => expect(loadImages).toBeInTheDocument());
await waitFor(() => expect(togglePortrait).toBeInTheDocument());
await waitFor(() => expect(toggleDirection).toBeInTheDocument());
await waitFor(() => expect(canvas.getByTestId('one')).toBeInTheDocument());
await waitFor(() => expect(canvas.getByTestId('two')).toBeInTheDocument());
await waitFor(() => expect(slider.getAttribute('aria-valuenow')).toBe('75'));

await user.click(loadImages);
await waitFor(() => expect(canvas.getByAltText('one')).toBeInTheDocument(), { timeout: 3000 });
await waitFor(() => expect(canvas.getByAltText('two')).toBeInTheDocument());
await waitFor(() => expect(slider.getAttribute('aria-valuenow')).toBe('75'));
await waitFor(() => expect(parseInt(window.getComputedStyle(slider).width)).toBe(128));
await waitFor(() => expect(parseInt(window.getComputedStyle(slider).height)).not.toBe(128));
await waitFor(() => expect(parseInt(window.getComputedStyle(slider).top)).toBe(96));

await user.click(togglePortrait);
await waitFor(() => expect(slider.getAttribute('aria-valuenow')).toBe('75'));
await waitFor(() => expect(parseInt(window.getComputedStyle(slider).width)).not.toBe(128));
await waitFor(() => expect(parseInt(window.getComputedStyle(slider).height)).toBe(128));
await waitFor(() => expect(parseInt(window.getComputedStyle(slider).left)).toBe(96));

await user.click(toggleDirection);
await waitFor(() => expect(slider.getAttribute('aria-valuenow')).toBe('75'));
await waitFor(() => expect(parseInt(window.getComputedStyle(slider).width)).not.toBe(128));
await waitFor(() => expect(parseInt(window.getComputedStyle(slider).height)).toBe(128));
await waitFor(() => expect(parseInt(window.getComputedStyle(slider).left)).toBe(96));
};
20 changes: 10 additions & 10 deletions lib/src/ReactCompareSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ export const ReactCompareSlider = forwardRef<
const [interactiveTarget, setInteractiveTarget] = useState<HTMLElement | null>();
/** The `position` value at *previous* render. */
const previousPosition = usePrevious(position);
const [didMount, setDidMount] = useState(false);

/** Sync the internal position and trigger position change callback if defined. */
const updateInternalPosition = useCallback(
Expand All @@ -82,9 +81,14 @@ export const ReactCompareSlider = forwardRef<
const clipElement = clipContainerRef.current as HTMLDivElement;
const { width, height, left, top } = rootElement.getBoundingClientRect();

// Early out when component has zero bounds.
if (width === 0 || height === 0) {
return;
}

const pixelPosition = portrait
? y - (isOffset ? top - window.pageYOffset : 0)
: x - (isOffset ? left - window.pageXOffset : 0);
? y - (isOffset ? top - window.scrollY : 0)
: x - (isOffset ? left - window.scrollX : 0);

/** Next position as percentage. */
const nextPosition = Math.min(
Expand All @@ -93,7 +97,7 @@ export const ReactCompareSlider = forwardRef<
);

// Skip position update if possible.
if (!alwaysUpdate && didMount) {
if (!alwaysUpdate) {
const boundsAreMet = portrait
? pixelPosition >= height || pixelPosition === 0
: pixelPosition >= width || pixelPosition === 0;
Expand Down Expand Up @@ -130,7 +134,7 @@ export const ReactCompareSlider = forwardRef<
onPositionChange(internalPosition.current);
}
},
[boundsPadding, didMount, onPositionChange, portrait],
[boundsPadding, onPositionChange, portrait],
);

// Update internal position when other user controllable props change.
Expand Down Expand Up @@ -248,11 +252,6 @@ export const ReactCompareSlider = forwardRef<
[keyboardIncrement, portrait, updateInternalPosition],
);

// Set mount state to ensure initial position setter is not skipped if the initial value is `100`.
useEffect(() => {
setDidMount(true);
}, []);

// Set target container for pointer events.
useEffect(() => {
setInteractiveTarget(
Expand Down Expand Up @@ -348,6 +347,7 @@ export const ReactCompareSlider = forwardRef<

const rootStyle: CSSProperties = {
position: 'relative',
display: 'flex',
overflow: 'hidden',
cursor: isDragging ? (portrait ? 'ns-resize' : 'ew-resize') : undefined,
touchAction: 'none',
Expand Down

0 comments on commit 646d88e

Please sign in to comment.