Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
dash>=2.0.0
dash_bootstrap_components==ci-fixes
dash==3.0.0rc1
dash_bootstrap_components==1.7.1-dev
gunicorn
markdown
pandas
Expand Down
64 changes: 28 additions & 36 deletions src/components/accordion/__tests__/Accordion.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

import React from 'react';
import {render} from '@testing-library/react';
import {act, render, screen, waitFor} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Accordion from '../Accordion';
import AccordionItem from '../AccordionItem';
Expand Down Expand Up @@ -91,7 +91,8 @@ describe('Accordion', () => {
).not.toHaveClass('show');
});

test('tracks most recently clicked item with "active_item" prop', () => {
test('tracks most recently clicked item with "active_item" prop', async () => {
const user = userEvent.setup({advanceTimers: jest.advanceTimersByTime});
const mockSetProps = jest.fn();
const {container, rerender} = render(
<Accordion setProps={mockSetProps} active_item="item-0">
Expand All @@ -109,9 +110,7 @@ describe('Accordion', () => {
accordionItems.children[1].querySelector('div.accordion-collapse')
).not.toHaveClass('show');

userEvent.click(
accordionItems.children[1].querySelector('h2.accordion-header > button')
);
await user.click(await screen.findByText('item-title-2'));
expect(mockSetProps.mock.calls).toHaveLength(1);

rerender(
Expand All @@ -120,7 +119,7 @@ describe('Accordion', () => {
<AccordionItem title="item-title-2">item-content-2</AccordionItem>
</Accordion>
);
jest.runAllTimers();
act(() => jest.runAllTimers());

expect(
accordionItems.children[0].querySelector('div.accordion-collapse')
Expand All @@ -130,9 +129,7 @@ describe('Accordion', () => {
).toHaveClass('show');

// clicking on an open item closes it
userEvent.click(
accordionItems.children[1].querySelector('h2.accordion-header > button')
);
await user.click(await screen.findByText('item-title-2'));
expect(mockSetProps.mock.calls).toHaveLength(2);

rerender(
Expand All @@ -141,7 +138,7 @@ describe('Accordion', () => {
<AccordionItem title="item-title-2">item-content-2</AccordionItem>
</Accordion>
);
jest.runAllTimers();
act(() => jest.runAllTimers());

expect(
accordionItems.children[0].querySelector('div.accordion-collapse')
Expand All @@ -151,7 +148,8 @@ describe('Accordion', () => {
).not.toHaveClass('show');
});

test('keeps item open with "always_open" prop', () => {
test('keeps item open with "always_open" prop', async () => {
const user = userEvent.setup({advanceTimers: jest.advanceTimersByTime});
const mockSetProps = jest.fn();

const {container} = render(
Expand All @@ -172,34 +170,31 @@ describe('Accordion', () => {
).not.toHaveClass('show');

// Click on the second item
userEvent.click(
accordionItems.children[1].querySelector('h2.accordion-header > button')
);
await user.click(await screen.findByText('item-title-2'));
expect(mockSetProps.mock.calls).toHaveLength(1);

// Allow the click to take effect
jest.runAllTimers();
// wait for transition to complete
await waitFor(() =>
expect(
accordionItems.children[1].querySelector('div.accordion-collapse')
).toHaveClass('show')
);

// Check just the second item is open
expect(
accordionItems.children[0].querySelector('div.accordion-collapse')
).not.toHaveClass('show');
expect(
accordionItems.children[1].querySelector('div.accordion-collapse')
).toHaveClass('show');

// Click on the first item
userEvent.click(
accordionItems.children[0].querySelector('h2.accordion-header > button')
);
await user.click(await screen.findByText('item-title-1'));
expect(mockSetProps.mock.calls).toHaveLength(2);
// Allow the click to take effect
jest.runAllTimers();

// Check that the first child is now open, and the second remains open
expect(
accordionItems.children[0].querySelector('div.accordion-collapse')
).toHaveClass('show');
await waitFor(() =>
expect(
accordionItems.children[0].querySelector('div.accordion-collapse')
).toHaveClass('show')
);
expect(
accordionItems.children[1].querySelector('div.accordion-collapse')
).toHaveClass('show');
Expand All @@ -224,7 +219,8 @@ describe('Accordion', () => {
).toHaveClass('show');
});

test('tracks most recently clicked item with "active_item" prop when always_open', () => {
test('tracks most recently clicked item with "active_item" prop when always_open', async () => {
const user = userEvent.setup({advanceTimers: jest.advanceTimersByTime});
const mockSetProps = jest.fn();
const {container, rerender} = render(
<Accordion setProps={mockSetProps} active_item={['item-0']} always_open>
Expand All @@ -248,9 +244,7 @@ describe('Accordion', () => {
).not.toHaveClass('show');

// Click the middle option
userEvent.click(
accordionItems.children[1].querySelector('h2.accordion-header > button')
);
await user.click(await screen.getByText('item-title-2'));
expect(mockSetProps.mock.calls).toHaveLength(1);

rerender(
Expand All @@ -264,7 +258,7 @@ describe('Accordion', () => {
<AccordionItem title="item-title-3">item-content-3</AccordionItem>
</Accordion>
);
jest.runAllTimers();
act(() => jest.runAllTimers());

// Check first option stayed open, middle option now open but third still
// closed
Expand All @@ -279,9 +273,7 @@ describe('Accordion', () => {
).not.toHaveClass('show');

// clicking on 1st item closes it, but keeps second item open and 3rd closed
userEvent.click(
accordionItems.children[0].querySelector('h2.accordion-header > button')
);
await user.click(await screen.getByText('item-title-1'));
expect(mockSetProps.mock.calls).toHaveLength(2);

rerender(
Expand All @@ -295,7 +287,7 @@ describe('Accordion', () => {
<AccordionItem title="item-title-3">item-content-3</AccordionItem>
</Accordion>
);
jest.runAllTimers();
act(() => jest.runAllTimers());

// Check that 1 and 3 now closed, and 2 is open
expect(
Expand Down
15 changes: 9 additions & 6 deletions src/components/badge/__tests__/Badge.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,31 +56,34 @@ describe('Badge', () => {
expect(badge.getAttribute('href')).toBe(href);
});

test('tracks clicks with n_clicks', () => {
test('tracks clicks with n_clicks', async () => {
const user = userEvent.setup();
const mockSetProps = jest.fn();
const badge = render(<Badge setProps={mockSetProps}>Clickable</Badge>);

expect(mockSetProps.mock.calls).toHaveLength(0);

userEvent.click(badge.getByText('Clickable'));
await user.click(badge.getByText('Clickable'));

expect(mockSetProps.mock.calls).toHaveLength(1);
expect(mockSetProps.mock.calls[0][0].n_clicks).toBe(1);
});

test('relative links are internal by default', () => {
test('relative links are internal by default', async () => {
const user = userEvent.setup();
const badge = render(<Badge href="/relative">Clickable</Badge>);

const mockEventListener = jest.fn();
window.addEventListener('_dashprivate_pushstate', mockEventListener);
window.scrollTo = jest.fn();

expect(mockEventListener.mock.calls).toHaveLength(0);
userEvent.click(badge.getByText('Clickable'));
await user.click(badge.getByText('Clickable'));
expect(mockEventListener.mock.calls).toHaveLength(1);
});

test('relative links are external with external_link=true', () => {
test('relative links are external with external_link=true', async () => {
const user = userEvent.setup();
const badge = render(
<Badge href="/relative" external_link>
Clickable
Expand All @@ -92,7 +95,7 @@ describe('Badge', () => {
window.scrollTo = jest.fn();

expect(mockEventListener.mock.calls).toHaveLength(0);
userEvent.click(badge.getByText('Clickable'));
await user.click(badge.getByText('Clickable'));
expect(mockEventListener.mock.calls).toHaveLength(0);
});
});
47 changes: 20 additions & 27 deletions src/components/button/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,26 @@ import Link from '../../private/Link';
* Use the `n_clicks` prop to trigger callbacks when the button has been
* clicked.
*/
const Button = props => {
const {
children,
disabled,
href,
loading_state,
setProps,
n_clicks,
target,
type,
download,
name,
value,
className,
class_name,
color,
outline,
onClick,
rel,
...otherProps
} = props;

const Button = ({
children,
disabled,
href,
loading_state,
setProps,
target,
type,
download,
name,
value,
className,
class_name,
color,
outline,
onClick,
rel,
n_clicks = 0,
...otherProps
}) => {
const incrementClicks = () => {
if (!disabled && setProps) {
setProps({
Expand Down Expand Up @@ -73,11 +71,6 @@ const Button = props => {
);
};

Button.dashPersistence = {
n_clicks: 0,
n_clicks_timestamp: -1
};

Button.propTypes = {
/**
* The ID of this component, used to identify dash components
Expand Down
20 changes: 12 additions & 8 deletions src/components/button/__tests__/Button.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,21 @@ describe('Button', () => {
);
});

test('tracks clicks with n_clicks', () => {
test('tracks clicks with n_clicks', async () => {
const user = userEvent.setup();
const mockSetProps = jest.fn();
const button = render(<Button setProps={mockSetProps}>Clickable</Button>);

expect(mockSetProps.mock.calls).toHaveLength(0);

userEvent.click(button.getByText('Clickable'));
await user.click(button.getByText('Clickable'));

expect(mockSetProps.mock.calls).toHaveLength(1);
expect(mockSetProps.mock.calls[0][0].n_clicks).toBe(1);
});

test("doesn't track clicks if disabled", () => {
test("doesn't track clicks if disabled", async () => {
const user = userEvent.setup();
const mockSetProps = jest.fn();
const button = render(
<Button disabled setProps={mockSetProps}>
Expand All @@ -106,24 +108,26 @@ describe('Button', () => {

expect(mockSetProps.mock.calls).toHaveLength(0);

userEvent.click(button.getByText('Clickable'));
await user.click(button.getByText('Clickable'));

expect(mockSetProps.mock.calls).toHaveLength(0);
});

test('relative links are internal by default', () => {
test('relative links are internal by default', async () => {
const user = userEvent.setup();
const button = render(<Button href="/relative">Clickable</Button>);

const mockEventListener = jest.fn();
window.addEventListener('_dashprivate_pushstate', mockEventListener);
window.scrollTo = jest.fn();

expect(mockEventListener.mock.calls).toHaveLength(0);
userEvent.click(button.getByText('Clickable'));
await user.click(button.getByText('Clickable'));
expect(mockEventListener.mock.calls).toHaveLength(1);
});

test('relative links are external with external_link=true', () => {
test('relative links are external with external_link=true', async () => {
const user = userEvent.setup();
const button = render(
<Button href="/relative" external_link>
Clickable
Expand All @@ -135,7 +139,7 @@ describe('Button', () => {
window.scrollTo = jest.fn();

expect(mockEventListener.mock.calls).toHaveLength(0);
userEvent.click(button.getByText('Clickable'));
await user.click(button.getByText('Clickable'));
expect(mockEventListener.mock.calls).toHaveLength(0);
});
});
Loading
Loading