Skip to content

Commit

Permalink
feat(ClickOutsideListener): refactor and allow for ref targets (#1687)
Browse files Browse the repository at this point in the history
  • Loading branch information
scurker authored Sep 20, 2024
1 parent 8ff3885 commit 8f770f3
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 85 deletions.
2 changes: 1 addition & 1 deletion docs/pages/components/ClickOutsideListener.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import { ClickOutsideListener } from '@deque/cauldron-react'
},
{
name: 'target',
type: 'HTMLElement',
type: 'HTMLElement | React.RefObject | React.MutableRefObject',
description: 'Optional element to attach events to if wrapped child does not have an accessible ref.'
}
]}
Expand Down
124 changes: 106 additions & 18 deletions packages/react/src/components/ClickOutsideListener/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import ClickOutsideListener from './';

let wrapperNode: HTMLDivElement | null;
Expand All @@ -9,6 +10,7 @@ beforeEach(() => {
wrapperNode = document.createElement('div');
wrapperNode.innerHTML = `
<a href="#foo" data-testid="link">Click Me!</a>
<div id="target"></div>
<div id="#mount"></div>
`;
document.body.appendChild(wrapperNode);
Expand All @@ -35,16 +37,20 @@ test('should render children with the text when using ClickOutsideListener', ()
expect(renderedChild).toBeInTheDocument();
});

test('should call onClickOutside when clicked outside', () => {
test('should call onClickOutside when clicked outside', async () => {
const onClickOutside = jest.fn();
const user = userEvent.setup();

render(
<ClickOutsideListener onClickOutside={onClickOutside}>
<div>bar</div>
</ClickOutsideListener>
</ClickOutsideListener>,
{
container: mountNode as HTMLElement
}
);

fireEvent.click(screen.getByRole('link', { name: 'Click Me!' }));
await user.click(screen.getByRole('link', { name: 'Click Me!' }));
expect(onClickOutside).toBeCalled();
});

Expand All @@ -54,7 +60,10 @@ test('should call onClickOutside with event', () => {
render(
<ClickOutsideListener onClickOutside={onClickOutside}>
<div>bar</div>
</ClickOutsideListener>
</ClickOutsideListener>,
{
container: mountNode as HTMLElement
}
);

const event = new MouseEvent('click', { bubbles: true });
Expand All @@ -68,24 +77,31 @@ test('should call onClickOutside when touched outside', () => {
render(
<ClickOutsideListener onClickOutside={onClickOutside}>
<div>bar</div>
</ClickOutsideListener>
</ClickOutsideListener>,
{
container: mountNode as HTMLElement
}
);

const event = new TouchEvent('touchend', { bubbles: true });
fireEvent(screen.getByTestId('link'), event);
expect(onClickOutside).toHaveBeenCalledTimes(1);
});

test('should not call onClickOutside when clicked inside', () => {
test('should not call onClickOutside when clicked inside', async () => {
const onClickOutside = jest.fn();
const user = userEvent.setup();

render(
<ClickOutsideListener onClickOutside={onClickOutside}>
<div>Click me!</div>
</ClickOutsideListener>
</ClickOutsideListener>,
{
container: mountNode as HTMLElement
}
);

fireEvent.click(screen.getByText('Click me!'));
await user.click(screen.getByText('Click me!'));
expect(onClickOutside).not.toBeCalled();
});

Expand All @@ -95,7 +111,10 @@ test('should not call onClickOutside when touched inside', () => {
render(
<ClickOutsideListener onClickOutside={onClickOutside}>
<div data-testid="test">Touch me!</div>
</ClickOutsideListener>
</ClickOutsideListener>,
{
container: mountNode as HTMLElement
}
);

const event = new TouchEvent('touchend');
Expand All @@ -112,7 +131,10 @@ test('should allow mouseEvent to be changed', () => {
mouseEvent="mousedown"
>
<div>bar</div>
</ClickOutsideListener>
</ClickOutsideListener>,
{
container: mountNode as HTMLElement
}
);

const event = new MouseEvent('mousedown', { bubbles: true });
Expand All @@ -126,7 +148,10 @@ test('should allow mouseEvent to be false', () => {
render(
<ClickOutsideListener onClickOutside={onClickOutside} mouseEvent={false}>
<div>bar</div>
</ClickOutsideListener>
</ClickOutsideListener>,
{
container: mountNode as HTMLElement
}
);

const event = new MouseEvent('click', { bubbles: true });
Expand All @@ -143,7 +168,10 @@ test('should allow touchEvent to be changed', () => {
touchEvent="touchstart"
>
<div>div</div>
</ClickOutsideListener>
</ClickOutsideListener>,
{
container: mountNode as HTMLElement
}
);

const event = new TouchEvent('touchstart', { bubbles: true });
Expand All @@ -157,24 +185,31 @@ test('should allow touchEvent to be false', () => {
render(
<ClickOutsideListener onClickOutside={onClickOutside} touchEvent={false}>
<div>div</div>
</ClickOutsideListener>
</ClickOutsideListener>,
{
container: mountNode as HTMLElement
}
);

const event = new TouchEvent('touchend', { bubbles: true });
fireEvent(screen.getByTestId('link'), event);
expect(onClickOutside).not.toBeCalled();
});

test('should remove event listeners when props change', () => {
test('should remove event listeners when props change', async () => {
const onClickOutside = jest.fn();
const user = userEvent.setup();

const { rerender } = render(
<ClickOutsideListener onClickOutside={onClickOutside}>
<div>bar</div>
</ClickOutsideListener>
</ClickOutsideListener>,
{
container: mountNode as HTMLElement
}
);

fireEvent.click(screen.getByTestId('link'));
await user.click(screen.getByTestId('link'));
expect(onClickOutside).toHaveBeenCalledTimes(1);

rerender(
Expand All @@ -183,7 +218,7 @@ test('should remove event listeners when props change', () => {
</ClickOutsideListener>
);

fireEvent.click(screen.getByTestId('link'));
await user.click(screen.getByTestId('link'));
expect(onClickOutside).toHaveBeenCalledTimes(1);
});

Expand All @@ -194,9 +229,62 @@ test('should not remove event listeners when event props do not change', () => {
const { getByTestId } = render(
<ClickOutsideListener onClickOutside={onClickOutside} mouseEvent="click">
<div>bar</div>
</ClickOutsideListener>
</ClickOutsideListener>,
{
container: mountNode as HTMLElement
}
);

fireEvent.click(getByTestId('link'));
expect(removeEventListeners).not.toBeCalled();
});

test('should allow for HTMLElement target', async () => {
const user = userEvent.setup();
const onClickOutside = jest.fn();
const target = document.getElementById('target') as HTMLElement;

const { getByTestId } = render(
<ClickOutsideListener
onClickOutside={onClickOutside}
mouseEvent="click"
target={target}
>
<>
<div data-testid="child">bar</div>
</>
</ClickOutsideListener>,
{
container: mountNode as HTMLElement
}
);

await user.click(getByTestId('link'));
expect(onClickOutside).toHaveBeenCalledTimes(1);
});

test('should allow for ref target', async () => {
const user = userEvent.setup();
const onClickOutside = jest.fn();
const refTarget =
React.createRef<HTMLElement>() as React.MutableRefObject<HTMLElement | null>;
refTarget.current = document.getElementById('target');

const { getByTestId } = render(
<ClickOutsideListener
onClickOutside={onClickOutside}
mouseEvent="click"
target={refTarget}
>
<>
<div data-testid="child">bar</div>
</>
</ClickOutsideListener>,
{
container: mountNode as HTMLElement
}
);

await user.click(getByTestId('link'));
expect(onClickOutside).toHaveBeenCalledTimes(1);
});
Loading

0 comments on commit 8f770f3

Please sign in to comment.