diff --git a/.changeset/blue-beds-deliver.md b/.changeset/blue-beds-deliver.md deleted file mode 100644 index 627d2b4acc..0000000000 --- a/.changeset/blue-beds-deliver.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -'@talend/react-faceted-search': major -'@talend/design-system': major -'@talend/react-flow-designer': major -'@talend/router-bridge': major -'@talend/react-storybook-cmf': major -'@talend/react-bootstrap': major -'@talend/react-cmf-router': major -'@talend/react-components': major -'@talend/react-containers': major -'@talend/react-cmf-cqrs': major -'@talend/react-dataviz': major -'@talend/react-stepper': major -'@talend/react-forms': major -'@talend/icons': major -'@talend/react-sagas': major -'@talend/react-a11y': major -'@talend/http': major -'@talend/react-cmf': major ---- - -React: Upgrade to react 18 and @types/react 18 diff --git a/.changeset/bright-dodos-deny.md b/.changeset/bright-dodos-deny.md deleted file mode 100644 index 04b4216a50..0000000000 --- a/.changeset/bright-dodos-deny.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -'@talend/scripts-core': major -'@talend/scripts-config-jest': major ---- - -- fix: enforce timer at the end of all tests. -- feat: mock ally.js has it uses unsupported dom method from jsdom. -- feat: add jest-axe configuration - - -To support floating-ui/react following issue we have decided to add an afterAll to let floating-ui finish stuff -https://github.com/floating-ui/floating-ui/issues/1908 - - -Breaking changes: - -you may have tests where you ask for jest.useFakeTimer without go back to real at some point. This is a side effect and it is not compatible with our change to support floating-ui. - -```diff -jest.useFakeTimers() -render(); - - // then - expect(screen.getByRole('button')).toBeInTheDocument(); - }); - - it('Should have type=button by default', () => { - // when - render(); - - // then - expect(screen.getByRole('button')).toHaveAttribute('type', 'button'); - }); - - it('Should show the type if passed one', () => { - // when - render(); - - // then - expect(screen.getByRole('button')).toHaveAttribute('type', 'submit'); - }); - - it('Should output an anchor if called with a href', () => { - // when - const href = '/url'; - render(); - - // then - expect(screen.getByRole('link')).toHaveAttribute('href', href); - }); - - it('Should call onClick callback', () => { - // given - const onClick = jest.fn(); - render(); - - // when - userEvent.click(screen.getByRole('button')); - - // then - expect(onClick).toHaveBeenCalled(); - }); - - it('Should be disabled', () => { - // when - render(); - - // then - expect(screen.getByRole('button')).toBeDisabled(); - }); - - it('Should be disabled link', () => { - // when - render( - - ); - - // then - const link = screen.getByRole('button'); - expect(link.tagName).toBe('A'); - expect(link).toHaveClass('disabled'); - }); - - it('Should have block class', () => { - // when - render(); - - // then - expect(screen.getByRole('button')).toHaveClass('btn-block'); - }); - - it('Should apply bsStyle class', () => { - // when - render(); - - // then - expect(screen.getByRole('button')).toHaveClass('btn-danger'); - }); - - it('Should honour additional classes passed in, adding not overriding', () => { - // when - render( - - ); - - // then - expect(screen.getByRole('button')).toHaveClass('btn-danger'); - expect(screen.getByRole('button')).toHaveClass('bob'); - }); - - it('Should default to bsStyle="default"', () => { - // when - render(); - - // then - expect(screen.getByRole('button')).toHaveClass('btn-default'); - }); - - it('Should be active', () => { - // when - render(); - - // then - expect(screen.getByRole('button')).toHaveClass('active'); - }); + it('Should output a button', () => { + // when + render(); + + // then + expect(screen.getByRole('button')).toBeInTheDocument(); + }); + + it('Should have type=button by default', () => { + // when + render(); + + // then + expect(screen.getByRole('button')).toHaveAttribute('type', 'button'); + }); + + it('Should show the type if passed one', () => { + // when + render(); + + // then + expect(screen.getByRole('button')).toHaveAttribute('type', 'submit'); + }); + + it('Should output an anchor if called with a href', () => { + // when + const href = '/url'; + render(); + + // then + expect(screen.getByRole('link')).toHaveAttribute('href', href); + }); + + it('Should call onClick callback', async () => { + const user = userEvent.setup(); + + // given + const onClick = jest.fn(); + render(); + + // when + await user.click(screen.getByRole('button')); + + // then + expect(onClick).toHaveBeenCalled(); + }); + + it('Should be disabled', () => { + // when + render(); + + // then + expect(screen.getByRole('button')).toBeDisabled(); + }); + + it('Should be disabled link', () => { + // when + render( + , + ); + + // then + const link = screen.getByRole('button'); + expect(link.tagName).toBe('A'); + expect(link).toHaveClass('disabled'); + }); + + it('Should have block class', () => { + // when + render(); + + // then + expect(screen.getByRole('button')).toHaveClass('btn-block'); + }); + + it('Should apply bsStyle class', () => { + // when + render(); + + // then + expect(screen.getByRole('button')).toHaveClass('btn-danger'); + }); + + it('Should honour additional classes passed in, adding not overriding', () => { + // when + render( + , + ); + + // then + expect(screen.getByRole('button')).toHaveClass('btn-danger'); + expect(screen.getByRole('button')).toHaveClass('bob'); + }); + + it('Should default to bsStyle="default"', () => { + // when + render(); + + // then + expect(screen.getByRole('button')).toHaveClass('btn-default'); + }); + + it('Should be active', () => { + // when + render(); + + // then + expect(screen.getByRole('button')).toHaveClass('active'); + }); }); diff --git a/fork/react-bootstrap/src/Carousel.test.js b/fork/react-bootstrap/src/Carousel.test.js index 0225227497..a908f0de81 100644 --- a/fork/react-bootstrap/src/Carousel.test.js +++ b/fork/react-bootstrap/src/Carousel.test.js @@ -1,307 +1,300 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; + +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import Carousel from './Carousel'; describe('', () => { - const items = [ - Item 1 content, - Item 2 content, - ]; - - it('Should show the correct item', () => { - // when - render({items}); - - // then - const item1 = screen.getByText('Item 1 content'); - const item2 = screen.getByText('Item 2 content'); - expect(item1).toBeInTheDocument(); - expect(item1).not.toHaveClass('active'); - expect(item2).toBeInTheDocument(); - expect(item2).toHaveClass('active'); - }); - - it('Should show the correct item with defaultActiveIndex', () => { - // when - render({items}); - - // then - const item1 = screen.getByText('Item 1 content'); - const item2 = screen.getByText('Item 2 content'); - expect(item1).toBeInTheDocument(); - expect(item1).not.toHaveClass('active'); - expect(item2).toBeInTheDocument(); - expect(item2).toHaveClass('active'); - const list = screen.getByRole('list'); - expect(list).toBeInTheDocument(); - expect(list).toHaveClass('carousel-indicators'); - expect(list.querySelectorAll('li')).toHaveLength(2); - }); - - it('Should handle null children', () => { - // when - render( - - Item 1 content - {null} - {false} - Item 2 content - - ); - - // then - const item1 = screen.getByText('Item 1 content'); - const item2 = screen.getByText('Item 2 content'); - expect(item1).toBeInTheDocument(); - expect(item1).not.toHaveClass('active'); - expect(item2).toBeInTheDocument(); - expect(item2).toHaveClass('active'); - const list = screen.getByRole('list'); - expect(list).toBeInTheDocument(); - expect(list).toHaveClass('carousel-indicators'); - expect(list.querySelectorAll('li')).toHaveLength(2); - }); - - it('Should call onSelect when indicator selected', () => { - // given - const onSelect = jest.fn(); - render( - - {items} - - ); - - // when - userEvent.click(screen.getAllByRole('listitem')[0]); - - // then - expect(onSelect).toBeCalledWith(0); - }); - - it('Should call onSelect with direction', () => { - // given - const onSelect = jest.fn((index, event) => {}); // force the event with direction by requiring event in callback - render( - - {items} - - ); - - // when - userEvent.click(screen.getAllByRole('listitem')[0]); - - // then - expect(onSelect).toBeCalled(); - expect(onSelect.mock.calls[0][0]).toBe(0); - expect(onSelect.mock.calls[0][1].direction).toBe('prev'); - }); - - it('Should call onSelect with direction when there is no event', () => { - // function onSelect(index, event) { - // expect(index).to.equal(0); - // expect(event.direction).to.equal('next'); - // expect(event.target).to.not.exist; - - // done(); - // } - - // given - const onSelect = jest.fn((index, event) => {}); - render( - - {items} - - ); - - // when - userEvent.click(screen.getByRole('button', { name: 'Next' })); - - // then - expect(onSelect).toBeCalled(); - expect(onSelect.mock.calls[0][0]).toBe(0); - expect(onSelect.mock.calls[0][1].direction).toBe('next'); - }); - - it('Should show back button control on the first image if wrap is true', () => { - // given - jest.useFakeTimers(); - render( - - {items} - - ); - expect(screen.getByText('Item 1 content')).toHaveClass('active'); - - // when - userEvent.click(screen.getByRole('button', { name: 'Previous' })); - jest.runAllTimers(); - - // then - expect(screen.getByText('Item 2 content')).toHaveClass('active'); - jest.useRealTimers(); - }); - - it('Should show next button control on the last image if wrap is true', () => { - // given - jest.useFakeTimers(); - render( - - {items} - - ); - expect(screen.getByText('Item 2 content')).toHaveClass('active'); - - // when - userEvent.click(screen.getByRole('button', { name: 'Next' })); - jest.runAllTimers(); - - // then - expect(screen.getByText('Item 1 content')).toHaveClass('active'); - jest.useRealTimers(); - }); - - it('Should not show the prev button on the first image if wrap is false', () => { - // when - render( - - {items} - - ); - - // then - expect( - screen.queryByRole('button', { name: 'Previous' }) - ).not.toBeInTheDocument(); - }); - - it('Should not show the next button on the last image if wrap is false', () => { - // when - render( - - {items} - - ); - - // then - expect( - screen.queryByRole('button', { name: 'Next' }) - ).not.toBeInTheDocument(); - }); - - it('Should allow user to specify a previous and next icon', () => { - // when - render( - } - nextIcon={} - > - Item 1 content - Item 2 content - Item 3 content - - ); - - // then - expect( - screen.getByRole('button', { name: 'Previous' }).firstChild - ).toHaveClass('ficon-left'); - expect(screen.getByRole('button', { name: 'Next' }).firstChild).toHaveClass( - 'ficon-right' - ); - }); - - it('Should allow user to specify a previous and next SR label', () => { - // when - render( - - Item 1 content - Item 2 content - Item 3 content - - ); - - // then - expect( - screen.getByRole('button', { name: 'Previous awesomeness' }) - ).toBeInTheDocument(); - expect( - screen.getByRole('button', { name: 'Next awesomeness' }) - ).toBeInTheDocument(); - }); - - it('Should transition properly when slide animation is disabled', () => { - // given - render( - - {items} - - ); - expect(screen.getByText('Item 1 content')).toHaveClass('active'); - - // when - userEvent.click(screen.getByRole('button', { name: 'Next' })); - - // then - expect(screen.getByText('Item 2 content')).toHaveClass('active'); - - // when - userEvent.click(screen.getByRole('button', { name: 'Previous' })); - - // then - expect(screen.getByText('Item 1 content')).toHaveClass('active'); - }); - - it('Should render on update, default active item > new child length', () => { - // given - // default active is the 2nd item, which will be removed on - // subsequent render - const { rerender } = render( - {items} - ); - - expect(screen.getByText('Item 1 content')).not.toHaveClass('active'); - expect(screen.getByText('Item 2 content')).toHaveClass('active'); - expect(screen.getAllByRole('listitem')).toHaveLength(2); // carousel-indicators - - const fewerItems = items.slice(); - fewerItems.pop(); - - // when - rerender({fewerItems}); - - // then - expect(screen.getByText('Item 1 content')).toHaveClass('active'); - expect(screen.getAllByRole('listitem')).toHaveLength(1); - }); - - it('Should render on update, active item > new child length', () => { - // given - // default active is the 2nd item, which will be removed on - // subsequent render - const { rerender } = render({items}); - expect(screen.getByText('Item 1 content')).not.toHaveClass('active'); - expect(screen.getByText('Item 2 content')).toHaveClass('active'); - expect(screen.getAllByRole('listitem')).toHaveLength(2); // carousel-indicators - - const fewerItems = items.slice(); - fewerItems.pop(); - - // when - rerender({fewerItems}); - - // then - expect(screen.getByText('Item 1 content')).toHaveClass('active'); - expect(screen.getAllByRole('listitem')).toHaveLength(1); - }); + const items = [ + Item 1 content, + Item 2 content, + ]; + + it('Should show the correct item', () => { + // when + render({items}); + + // then + const item1 = screen.getByText('Item 1 content'); + const item2 = screen.getByText('Item 2 content'); + expect(item1).toBeInTheDocument(); + expect(item1).not.toHaveClass('active'); + expect(item2).toBeInTheDocument(); + expect(item2).toHaveClass('active'); + }); + + it('Should show the correct item with defaultActiveIndex', () => { + // when + render({items}); + + // then + const item1 = screen.getByText('Item 1 content'); + const item2 = screen.getByText('Item 2 content'); + expect(item1).toBeInTheDocument(); + expect(item1).not.toHaveClass('active'); + expect(item2).toBeInTheDocument(); + expect(item2).toHaveClass('active'); + const list = screen.getByRole('list'); + expect(list).toBeInTheDocument(); + expect(list).toHaveClass('carousel-indicators'); + expect(list.querySelectorAll('li')).toHaveLength(2); + }); + + it('Should handle null children', () => { + // when + render( + + Item 1 content + {null} + {false} + Item 2 content + , + ); + + // then + const item1 = screen.getByText('Item 1 content'); + const item2 = screen.getByText('Item 2 content'); + expect(item1).toBeInTheDocument(); + expect(item1).not.toHaveClass('active'); + expect(item2).toBeInTheDocument(); + expect(item2).toHaveClass('active'); + const list = screen.getByRole('list'); + expect(list).toBeInTheDocument(); + expect(list).toHaveClass('carousel-indicators'); + expect(list.querySelectorAll('li')).toHaveLength(2); + }); + + it('Should call onSelect when indicator selected', async () => { + const user = userEvent.setup(); + + // given + const onSelect = jest.fn(); + render( + + {items} + , + ); + + // when + await user.click(screen.getAllByRole('listitem')[0]); + + // then + expect(onSelect).toHaveBeenCalledWith(0); + }); + + it('Should call onSelect with direction', async () => { + const user = userEvent.setup(); + + // given + const onSelect = jest.fn((index, event) => {}); // force the event with direction by requiring event in callback + render( + + {items} + , + ); + + // when + await user.click(screen.getAllByRole('listitem')[0]); + + // then + expect(onSelect).toHaveBeenCalled(); + expect(onSelect.mock.calls[0][0]).toBe(0); + expect(onSelect.mock.calls[0][1].direction).toBe('prev'); + }); + + it('Should call onSelect with direction when there is no event', async () => { + const user = userEvent.setup(); + + // function onSelect(index, event) { + // expect(index).to.equal(0); + // expect(event.direction).to.equal('next'); + // expect(event.target).to.not.exist; + + // done(); + // } + + // given + const onSelect = jest.fn((index, event) => {}); + render( + + {items} + , + ); + + // when + await user.click(screen.getByRole('button', { name: 'Next' })); + + // then + expect(onSelect).toHaveBeenCalled(); + expect(onSelect.mock.calls[0][0]).toBe(0); + expect(onSelect.mock.calls[0][1].direction).toBe('next'); + }); + + it('Should show back button control on the first image if wrap is true', async () => { + const user = userEvent.setup(); + + // given + render( + + {items} + , + ); + expect(screen.getByText('Item 1 content')).toHaveClass('active'); + + // when + await user.click(screen.getByRole('button', { name: 'Previous' })); + + // then + await waitFor(() => expect(screen.getByText('Item 2 content')).toHaveClass('active')); + }); + + it('Should show next button control on the last image if wrap is true', async () => { + const user = userEvent.setup(); + + // given + render( + + {items} + , + ); + expect(screen.getByText('Item 2 content')).toHaveClass('active'); + + // when + await user.click(screen.getByRole('button', { name: 'Next' })); + + // then + await waitFor(() => expect(screen.getByText('Item 1 content')).toHaveClass('active')); + }); + + it('Should not show the prev button on the first image if wrap is false', () => { + // when + render( + + {items} + , + ); + + // then + expect(screen.queryByRole('button', { name: 'Previous' })).not.toBeInTheDocument(); + }); + + it('Should not show the next button on the last image if wrap is false', () => { + // when + render( + + {items} + , + ); + + // then + expect(screen.queryByRole('button', { name: 'Next' })).not.toBeInTheDocument(); + }); + + it('Should allow user to specify a previous and next icon', () => { + // when + render( + } + nextIcon={} + > + Item 1 content + Item 2 content + Item 3 content + , + ); + + // then + expect(screen.getByRole('button', { name: 'Previous' }).firstChild).toHaveClass('ficon-left'); + expect(screen.getByRole('button', { name: 'Next' }).firstChild).toHaveClass('ficon-right'); + }); + + it('Should allow user to specify a previous and next SR label', () => { + // when + render( + + Item 1 content + Item 2 content + Item 3 content + , + ); + + // then + expect(screen.getByRole('button', { name: 'Previous awesomeness' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Next awesomeness' })).toBeInTheDocument(); + }); + + it('Should transition properly when slide animation is disabled', async () => { + const user = userEvent.setup(); + + // given + render( + + {items} + , + ); + expect(screen.getByText('Item 1 content')).toHaveClass('active'); + + // when + await user.click(screen.getByRole('button', { name: 'Next' })); + + // then + expect(screen.getByText('Item 2 content')).toHaveClass('active'); + + // when + await user.click(screen.getByRole('button', { name: 'Previous' })); + + // then + expect(screen.getByText('Item 1 content')).toHaveClass('active'); + }); + + it('Should render on update, default active item > new child length', () => { + // given + // default active is the 2nd item, which will be removed on + // subsequent render + const { rerender } = render({items}); + + expect(screen.getByText('Item 1 content')).not.toHaveClass('active'); + expect(screen.getByText('Item 2 content')).toHaveClass('active'); + expect(screen.getAllByRole('listitem')).toHaveLength(2); // carousel-indicators + + const fewerItems = items.slice(); + fewerItems.pop(); + + // when + rerender({fewerItems}); + + // then + expect(screen.getByText('Item 1 content')).toHaveClass('active'); + expect(screen.getAllByRole('listitem')).toHaveLength(1); + }); + + it('Should render on update, active item > new child length', () => { + // given + // default active is the 2nd item, which will be removed on + // subsequent render + const { rerender } = render({items}); + expect(screen.getByText('Item 1 content')).not.toHaveClass('active'); + expect(screen.getByText('Item 2 content')).toHaveClass('active'); + expect(screen.getAllByRole('listitem')).toHaveLength(2); // carousel-indicators + + const fewerItems = items.slice(); + fewerItems.pop(); + + // when + rerender({fewerItems}); + + // then + expect(screen.getByText('Item 1 content')).toHaveClass('active'); + expect(screen.getAllByRole('listitem')).toHaveLength(1); + }); }); diff --git a/fork/react-bootstrap/src/CloseButton.test.js b/fork/react-bootstrap/src/CloseButton.test.js index a6dd026600..fe1fdedbc5 100644 --- a/fork/react-bootstrap/src/CloseButton.test.js +++ b/fork/react-bootstrap/src/CloseButton.test.js @@ -1,75 +1,78 @@ import React from 'react'; + import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import CloseButton from './CloseButton'; describe('', () => { - it('Should output a button', () => { - // when - const onClick = jest.fn(); - render(); - - // then - expect(screen.getByRole('button')).toBeInTheDocument(); - }); - - it('Should have type=button by default', () => { - // when - const onClick = jest.fn(); - render(); - - // then - expect(screen.getByRole('button')).toHaveAttribute('type', 'button'); - }); - - it('Should have class=close by default', () => { - // when - const onClick = jest.fn(); - render(); - - // then - expect(screen.getByRole('button')).toHaveClass('close'); - }); - - it('Should call onClick callback', () => { - // given - const onClick = jest.fn(); - render(); - expect(onClick).not.toHaveBeenCalled(); - - // when - userEvent.click(screen.getByRole('button')); - - // then - expect(onClick).toHaveBeenCalled(); - }); - - it('Should have a span with aria-hidden=true', () => { - // when - const onClick = jest.fn(); - render(); - - // then - expect(screen.getByText('×')).toHaveAttribute('aria-hidden', 'true'); - }); - - it('Should have a span with class=sr-only', () => { - // when - const onClick = jest.fn(); - render(); - - // then - expect(screen.getByText('Close')).toHaveClass('sr-only'); - }); - - it('Should have a span with the custom text of the label', () => { - // when - const onClick = jest.fn(); - const label = 'Close Item'; - render(); - - // then - expect(screen.getByText(label)).toBeInTheDocument(); - }); + it('Should output a button', () => { + // when + const onClick = jest.fn(); + render(); + + // then + expect(screen.getByRole('button')).toBeInTheDocument(); + }); + + it('Should have type=button by default', () => { + // when + const onClick = jest.fn(); + render(); + + // then + expect(screen.getByRole('button')).toHaveAttribute('type', 'button'); + }); + + it('Should have class=close by default', () => { + // when + const onClick = jest.fn(); + render(); + + // then + expect(screen.getByRole('button')).toHaveClass('close'); + }); + + it('Should call onClick callback', async () => { + const user = userEvent.setup(); + + // given + const onClick = jest.fn(); + render(); + expect(onClick).not.toHaveBeenCalled(); + + // when + await user.click(screen.getByRole('button')); + + // then + expect(onClick).toHaveBeenCalled(); + }); + + it('Should have a span with aria-hidden=true', () => { + // when + const onClick = jest.fn(); + render(); + + // then + expect(screen.getByText('×')).toHaveAttribute('aria-hidden', 'true'); + }); + + it('Should have a span with class=sr-only', () => { + // when + const onClick = jest.fn(); + render(); + + // then + expect(screen.getByText('Close')).toHaveClass('sr-only'); + }); + + it('Should have a span with the custom text of the label', () => { + // when + const onClick = jest.fn(); + const label = 'Close Item'; + render(); + + // then + expect(screen.getByText(label)).toBeInTheDocument(); + }); }); diff --git a/fork/react-bootstrap/src/ControlLabel.test.js b/fork/react-bootstrap/src/ControlLabel.test.js index 3c7589d4d4..6dbde7b2a5 100644 --- a/fork/react-bootstrap/src/ControlLabel.test.js +++ b/fork/react-bootstrap/src/ControlLabel.test.js @@ -1,68 +1,69 @@ import React from 'react'; + import { render, screen } from '@testing-library/react'; import ControlLabel from './ControlLabel'; import FormGroup from './FormGroup'; describe('', () => { - const originalConsoleError = console.error; + const originalConsoleError = console.error; - beforeEach(() => { - console.error = jest.fn(); - }); + beforeEach(() => { + console.error = jest.fn(); + }); - afterEach(() => { - console.error = originalConsoleError; - }); + afterEach(() => { + console.error = originalConsoleError; + }); - it('should render correctly', () => { - // when - render( - - Label - - ); + it('should render correctly', () => { + // when + render( + + Label + , + ); - // then - const label = screen.getByText('Label'); - expect(label.tagName).toBe('LABEL'); - expect(label).toHaveClass('control-label'); - expect(label).toHaveClass('my-control-label'); - expect(label).toHaveAttribute('for', 'foo'); - }); + // then + const label = screen.getByText('Label'); + expect(label.tagName).toBe('LABEL'); + expect(label).toHaveClass('control-label'); + expect(label).toHaveClass('my-control-label'); + expect(label).toHaveAttribute('for', 'foo'); + }); - it('should respect srOnly', () => { - // when - render(Label); + it('should respect srOnly', () => { + // when + render(Label); - // then - expect(screen.getByText('Label')).toHaveClass('sr-only'); - }); + // then + expect(screen.getByText('Label')).toHaveClass('sr-only'); + }); - it('should use controlId for htmlFor', () => { - // when - render( - - Label - - ); + it('should use controlId for htmlFor', () => { + // when + render( + + Label + , + ); - // then - expect(screen.getByText('Label')).toHaveAttribute('for', 'foo'); - }); + // then + expect(screen.getByText('Label')).toHaveAttribute('for', 'foo'); + }); - it('should prefer explicit htmlFor', () => { - // when - render( - - Label - - ); + it('should prefer explicit htmlFor', () => { + // when + render( + + Label + , + ); - // then - expect(screen.getByText('Label')).toHaveAttribute('for', 'bar'); - expect(console.error).toBeCalledWith( - 'Warning: `controlId` is ignored on `` when `htmlFor` is specified.' - ); - }); + // then + expect(screen.getByText('Label')).toHaveAttribute('for', 'bar'); + expect(console.error).toHaveBeenCalledWith( + 'Warning: `controlId` is ignored on `` when `htmlFor` is specified.', + ); + }); }); diff --git a/fork/react-bootstrap/src/Dropdown.js b/fork/react-bootstrap/src/Dropdown.js index a992679e58..2e892904e8 100644 --- a/fork/react-bootstrap/src/Dropdown.js +++ b/fork/react-bootstrap/src/Dropdown.js @@ -1,10 +1,10 @@ +import React, { cloneElement } from 'react'; +import ReactDOM from 'react-dom'; + import classNames from 'classnames'; import activeElement from 'dom-helpers/activeElement'; import contains from 'dom-helpers/query/contains'; -import keycode from 'keycode'; -import React, { cloneElement } from 'react'; import PropTypes from 'prop-types'; -import ReactDOM from 'react-dom'; import all from 'prop-types-extra/lib/all'; import elementType from 'prop-types-extra/lib/elementType'; import isRequiredForA11y from 'prop-types-extra/lib/isRequiredForA11y'; @@ -14,7 +14,7 @@ import warning from 'warning'; import ButtonGroup from './ButtonGroup'; import DropdownMenu from './DropdownMenu'; import DropdownToggle from './DropdownToggle'; -import { bsClass as setBsClass, prefix } from './utils/bootstrapUtils'; +import { prefix, bsClass as setBsClass } from './utils/bootstrapUtils'; import createChainedFunction from './utils/createChainedFunction'; import { exclusiveRoles, requiredRoles } from './utils/PropTypes'; import ValidComponentChildren from './utils/ValidComponentChildren'; @@ -23,330 +23,316 @@ const TOGGLE_ROLE = DropdownToggle.defaultProps.bsRole; const MENU_ROLE = DropdownMenu.defaultProps.bsRole; const propTypes = { - /** - * The menu will open above the dropdown button, instead of below it. - */ - dropup: PropTypes.bool, - - /** - * An html id attribute, necessary for assistive technologies, such as screen readers. - * @type {string|number} - * @required - */ - id: isRequiredForA11y( - PropTypes.oneOfType([PropTypes.string, PropTypes.number]) - ), - - componentClass: elementType, - - /** - * The children of a Dropdown may be a `` or a ``. - * @type {node} - */ - children: all( - requiredRoles(TOGGLE_ROLE, MENU_ROLE), - exclusiveRoles(MENU_ROLE) - ), - - /** - * Whether or not component is disabled. - */ - disabled: PropTypes.bool, - - /** - * Align the menu to the right side of the Dropdown toggle - */ - pullRight: PropTypes.bool, - - /** - * Whether or not the Dropdown is visible. - * - * @controllable onToggle - */ - open: PropTypes.bool, - - defaultOpen: PropTypes.bool, - - /** - * A callback fired when the Dropdown wishes to change visibility. Called with the requested - * `open` value, the DOM event, and the source that fired it: `'click'`,`'keydown'`,`'rootClose'`, or `'select'`. - * - * ```js - * function(Boolean isOpen, Object event, { String source }) {} - * ``` - * @controllable open - */ - onToggle: PropTypes.func, - - /** - * A callback fired when a menu item is selected. - * - * ```js - * (eventKey: any, event: Object) => any - * ``` - */ - onSelect: PropTypes.func, - - /** - * If `'menuitem'`, causes the dropdown to behave like a menu item rather than - * a menu button. - */ - role: PropTypes.string, - - /** - * Which event when fired outside the component will cause it to be closed - * - * *Note: For custom dropdown components, you will have to pass the - * `rootCloseEvent` to `` in your custom dropdown menu - * component ([similarly to how it is implemented in ``](https://github.com/react-bootstrap/react-bootstrap/blob/v0.31.5/src/DropdownMenu.js#L115-L119)).* - */ - rootCloseEvent: PropTypes.oneOf(['click', 'mousedown']), - - /** - * @private - */ - onMouseEnter: PropTypes.func, - /** - * @private - */ - onMouseLeave: PropTypes.func, + /** + * The menu will open above the dropdown button, instead of below it. + */ + dropup: PropTypes.bool, + + /** + * An html id attribute, necessary for assistive technologies, such as screen readers. + * @type {string|number} + * @required + */ + id: isRequiredForA11y(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), + + componentClass: elementType, + + /** + * The children of a Dropdown may be a `` or a ``. + * @type {node} + */ + children: all(requiredRoles(TOGGLE_ROLE, MENU_ROLE), exclusiveRoles(MENU_ROLE)), + + /** + * Whether or not component is disabled. + */ + disabled: PropTypes.bool, + + /** + * Align the menu to the right side of the Dropdown toggle + */ + pullRight: PropTypes.bool, + + /** + * Whether or not the Dropdown is visible. + * + * @controllable onToggle + */ + open: PropTypes.bool, + + defaultOpen: PropTypes.bool, + + /** + * A callback fired when the Dropdown wishes to change visibility. Called with the requested + * `open` value, the DOM event, and the source that fired it: `'click'`,`'keydown'`,`'rootClose'`, or `'select'`. + * + * ```js + * function(Boolean isOpen, Object event, { String source }) {} + * ``` + * @controllable open + */ + onToggle: PropTypes.func, + + /** + * A callback fired when a menu item is selected. + * + * ```js + * (eventKey: any, event: Object) => any + * ``` + */ + onSelect: PropTypes.func, + + /** + * If `'menuitem'`, causes the dropdown to behave like a menu item rather than + * a menu button. + */ + role: PropTypes.string, + + /** + * Which event when fired outside the component will cause it to be closed + * + * *Note: For custom dropdown components, you will have to pass the + * `rootCloseEvent` to `` in your custom dropdown menu + * component ([similarly to how it is implemented in ``](https://github.com/react-bootstrap/react-bootstrap/blob/v0.31.5/src/DropdownMenu.js#L115-L119)).* + */ + rootCloseEvent: PropTypes.oneOf(['click', 'mousedown']), + + /** + * @private + */ + onMouseEnter: PropTypes.func, + /** + * @private + */ + onMouseLeave: PropTypes.func, }; const defaultProps = { - componentClass: ButtonGroup, + componentClass: ButtonGroup, }; class Dropdown extends React.Component { - constructor(props, context) { - super(props, context); - - this.handleClick = this.handleClick.bind(this); - this.handleKeyDown = this.handleKeyDown.bind(this); - this.handleClose = this.handleClose.bind(this); - - this._focusInDropdown = false; - this.lastOpenEventType = null; - } - - componentDidMount() { - this.focusNextOnOpen(); - } - - componentDidUpdate(prevProps) { - const { open } = this.props; - const prevOpen = prevProps.open; - - if (open && !prevOpen) { - this.focusNextOnOpen(); - } - - if (!open && prevOpen) { - this._focusInDropdown = contains( - ReactDOM.findDOMNode(this.menu), - activeElement(document) - ); - // if focus hasn't already moved from the menu let's return it - // to the toggle - if (this._focusInDropdown) { - this._focusInDropdown = false; - this.focus(); - } - } - } - - focus() { - const toggle = ReactDOM.findDOMNode(this.toggle); - - if (toggle && toggle.focus) { - toggle.focus(); - } - } - - focusNextOnOpen() { - const menu = this.menu; - - if (!menu || !menu.focusNext) { - return; - } - - if ( - this.lastOpenEventType === 'keydown' || - this.props.role === 'menuitem' - ) { - menu.focusNext(); - } - } - - handleClick(event) { - if (this.props.disabled) { - return; - } - - this.toggleOpen(event, { source: 'click' }); - } - - handleClose(event, eventDetails) { - if (!this.props.open) { - return; - } - - this.toggleOpen(event, eventDetails); - } - - handleKeyDown(event) { - if (this.props.disabled) { - return; - } - - switch (event.keyCode) { - case keycode.codes.down: - if (!this.props.open) { - this.toggleOpen(event, { source: 'keydown' }); - } else if (this.menu.focusNext) { - this.menu.focusNext(); - } - event.preventDefault(); - break; - case keycode.codes.esc: - case keycode.codes.tab: - this.handleClose(event, { source: 'keydown' }); - break; - default: - } - } - - toggleOpen(event, eventDetails) { - let open = !this.props.open; - - if (open) { - this.lastOpenEventType = eventDetails.source; - } - - if (this.props.onToggle) { - this.props.onToggle(open, event, eventDetails); - } - } - - renderMenu(child, { id, onSelect, rootCloseEvent, ...props }) { - let ref = (c) => { - this.menu = c; - }; - - if (typeof child.ref === 'string') { - warning( - false, - 'String refs are not supported on `` components. ' + - 'To apply a ref to the component use the callback signature:\n\n ' + - 'https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute' - ); - } else { - ref = createChainedFunction(child.ref, ref); - } - - return cloneElement(child, { - ...props, - ref, - labelledBy: id, - bsClass: prefix(props, 'menu'), - onClose: createChainedFunction(child.props.onClose, this.handleClose), - onSelect: createChainedFunction( - child.props.onSelect, - onSelect, - (key, event) => this.handleClose(event, { source: 'select' }) - ), - rootCloseEvent, - }); - } - - renderToggle(child, props) { - let ref = (c) => { - this.toggle = c; - }; - - if (typeof child.ref === 'string') { - warning( - false, - 'String refs are not supported on `` components. ' + - 'To apply a ref to the component use the callback signature:\n\n ' + - 'https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute' - ); - } else { - ref = createChainedFunction(child.ref, ref); - } - - return cloneElement(child, { - ...props, - ref, - bsClass: prefix(props, 'toggle'), - onClick: createChainedFunction(child.props.onClick, this.handleClick), - onKeyDown: createChainedFunction( - child.props.onKeyDown, - this.handleKeyDown - ), - }); - } - - render() { - const { - componentClass: Component, - id, - dropup, - disabled, - pullRight, - open, - onSelect, - role, - bsClass, - className, - rootCloseEvent, - children, - ...props - } = this.props; - - delete props.onToggle; - - const classes = { - [bsClass]: true, - open, - disabled, - }; - - if (dropup) { - classes[bsClass] = false; - classes.dropup = true; - } - - // This intentionally forwards bsSize and bsStyle (if set) to the - // underlying component, to allow it to render size and style variants. - - return ( - - {ValidComponentChildren.map(children, (child) => { - switch (child.props.bsRole) { - case TOGGLE_ROLE: - return this.renderToggle(child, { - id, - disabled, - open, - role, - bsClass, - }); - case MENU_ROLE: - return this.renderMenu(child, { - id, - open, - pullRight, - bsClass, - onSelect, - rootCloseEvent, - }); - default: - return child; - } - })} - - ); - } + constructor(props, context) { + super(props, context); + + this.handleClick = this.handleClick.bind(this); + this.handleKeyDown = this.handleKeyDown.bind(this); + this.handleClose = this.handleClose.bind(this); + + this._focusInDropdown = false; + this.lastOpenEventType = null; + } + + componentDidMount() { + this.focusNextOnOpen(); + } + + componentDidUpdate(prevProps) { + const { open } = this.props; + const prevOpen = prevProps.open; + + if (open && !prevOpen) { + this.focusNextOnOpen(); + } + + if (!open && prevOpen) { + this._focusInDropdown = contains(ReactDOM.findDOMNode(this.menu), activeElement(document)); + // if focus hasn't already moved from the menu let's return it + // to the toggle + if (this._focusInDropdown) { + this._focusInDropdown = false; + this.focus(); + } + } + } + + focus() { + const toggle = ReactDOM.findDOMNode(this.toggle); + + if (toggle && toggle.focus) { + toggle.focus(); + } + } + + focusNextOnOpen() { + const menu = this.menu; + + if (!menu || !menu.focusNext) { + return; + } + + if (this.lastOpenEventType === 'keydown' || this.props.role === 'menuitem') { + menu.focusNext(); + } + } + + handleClick(event) { + if (this.props.disabled) { + return; + } + + this.toggleOpen(event, { source: 'click' }); + } + + handleClose(event, eventDetails) { + if (!this.props.open) { + return; + } + + this.toggleOpen(event, eventDetails); + } + + handleKeyDown(event) { + if (this.props.disabled) { + return; + } + + switch (event.key) { + case 'Down': + case 'ArrowDown': + if (!this.props.open) { + this.toggleOpen(event, { source: 'keydown' }); + } else if (this.menu.focusNext) { + this.menu.focusNext(); + } + event.preventDefault(); + break; + case 'Esc': + case 'Escape': + case 'Tab': + this.handleClose(event, { source: 'keydown' }); + break; + default: + } + } + + toggleOpen(event, eventDetails) { + let open = !this.props.open; + + if (open) { + this.lastOpenEventType = eventDetails.source; + } + + if (this.props.onToggle) { + this.props.onToggle(open, event, eventDetails); + } + } + + renderMenu(child, { id, onSelect, rootCloseEvent, ...props }) { + let ref = c => { + this.menu = c; + }; + + if (typeof child.ref === 'string') { + warning( + false, + 'String refs are not supported on `` components. ' + + 'To apply a ref to the component use the callback signature:\n\n ' + + 'https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute', + ); + } else { + ref = createChainedFunction(child.ref, ref); + } + + return cloneElement(child, { + ...props, + ref, + labelledBy: id, + bsClass: prefix(props, 'menu'), + onClose: createChainedFunction(child.props.onClose, this.handleClose), + onSelect: createChainedFunction(child.props.onSelect, onSelect, (key, event) => + this.handleClose(event, { source: 'select' }), + ), + rootCloseEvent, + }); + } + + renderToggle(child, props) { + let ref = c => { + this.toggle = c; + }; + + if (typeof child.ref === 'string') { + warning( + false, + 'String refs are not supported on `` components. ' + + 'To apply a ref to the component use the callback signature:\n\n ' + + 'https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute', + ); + } else { + ref = createChainedFunction(child.ref, ref); + } + + return cloneElement(child, { + ...props, + ref, + bsClass: prefix(props, 'toggle'), + onClick: createChainedFunction(child.props.onClick, this.handleClick), + onKeyDown: createChainedFunction(child.props.onKeyDown, this.handleKeyDown), + }); + } + + render() { + const { + componentClass: Component, + id, + dropup, + disabled, + pullRight, + open, + onSelect, + role, + bsClass, + className, + rootCloseEvent, + children, + ...props + } = this.props; + + delete props.onToggle; + + const classes = { + [bsClass]: true, + open, + disabled, + }; + + if (dropup) { + classes[bsClass] = false; + classes.dropup = true; + } + + // This intentionally forwards bsSize and bsStyle (if set) to the + // underlying component, to allow it to render size and style variants. + + return ( + + {ValidComponentChildren.map(children, child => { + switch (child.props.bsRole) { + case TOGGLE_ROLE: + return this.renderToggle(child, { + id, + disabled, + open, + role, + bsClass, + }); + case MENU_ROLE: + return this.renderMenu(child, { + id, + open, + pullRight, + bsClass, + onSelect, + rootCloseEvent, + }); + default: + return child; + } + })} + + ); + } } Dropdown.propTypes = propTypes; diff --git a/fork/react-bootstrap/src/Dropdown.test.js b/fork/react-bootstrap/src/Dropdown.test.js index f4f804b3b7..d592965d19 100644 --- a/fork/react-bootstrap/src/Dropdown.test.js +++ b/fork/react-bootstrap/src/Dropdown.test.js @@ -1,732 +1,711 @@ /* eslint-disable react/no-string-refs */ + /* eslint-disable react/prop-types */ import { useState } from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; - -import keycode from 'keycode'; import ReactDOM from 'react-dom'; +import { fireEvent, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + import Dropdown from './Dropdown'; import Grid from './Grid'; import MenuItem from './MenuItem'; function CustomMenu({ children, ...props }) { - return ( -
- {children} -
- ); + return ( +
+ {children} +
+ ); } describe('', () => { - const dropdownChildren = [ - Child Title, - - Item 1 - Item 2 - Item 3 - Item 4 - , - ]; - - const simpleDropdown = ( - - {dropdownChildren} - - ); - - it('renders div with dropdown class', () => { - // when - render({dropdownChildren}); - - // then - const group = screen.getByRole('menu').parentElement; - expect(group.tagName).toBe('DIV'); - expect(group).toHaveClass('dropdown'); - expect(group).not.toHaveClass('dropup'); - }); - - it('renders div with dropup class', () => { - // when - render( - - {dropdownChildren} - - ); - - // then - const group = screen.getByRole('menu').parentElement; - expect(group.tagName).toBe('DIV'); - expect(group).not.toHaveClass('dropdown'); - expect(group).toHaveClass('dropup'); - }); - - it('renders toggle with Dropdown.Toggle', () => { - // when - render(simpleDropdown); - - // then - const toggle = screen.getByRole('button', { name: 'Child Title' }); - expect(toggle.tagName).toBe('BUTTON'); - expect(toggle).toHaveClass('btn btn-default dropdown-toggle'); - expect(toggle).toHaveAttribute('type', 'button'); - expect(toggle).toHaveAttribute('aria-expanded', 'false'); - }); - - it('renders dropdown toggle button caret', () => { - // when - render(simpleDropdown); - - // then - const btn = screen.getByRole('button', { name: 'Child Title' }); - expect(btn.querySelector('span.caret')).toBeTruthy(); - }); - - it('does not render toggle button caret', () => { - // when - render(Child Text); - - // then - const caret = screen.getByRole('button', { name: 'Child Text' }); - expect(caret.querySelector('.caret')).toBeFalsy(); - }); - - it('renders custom menu', () => { - // when - render( - - Child Text - - - Item 1 - - - ); - - // then - expect(screen.getByRole('menu')).toBeInTheDocument(); - expect(screen.getByRole('menu')).toHaveClass('custom-menu'); - }); - - it('forwards pullRight to menu', () => { - // when - render( - - {dropdownChildren} - - ); - - // then - expect(screen.getByRole('menu')).toHaveClass('dropdown-menu-right'); - }); - - // NOTE: The onClick event handler is invoked for both the Enter and Space - // keys as well since the component is a button. I cannot figure out how to - // get ReactTestUtils to simulate such though. - it('toggles open/closed when clicked', () => { - // given - render(simpleDropdown); - expect(screen.getByTestId('test-id')).not.toHaveClass('open'); - expect(screen.getByRole('button')).toHaveAttribute( - 'aria-expanded', - 'false' - ); - - // when - userEvent.click(screen.getByRole('button')); - - // then - expect(screen.getByTestId('test-id')).toHaveClass('open'); - expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'true'); - - // when - userEvent.click(screen.getByRole('button')); - - // then - expect(screen.getByTestId('test-id')).not.toHaveClass('open'); - expect(screen.getByRole('button')).toHaveAttribute( - 'aria-expanded', - 'false' - ); - }); - - it('closes when clicked outside', () => { - // given - render(simpleDropdown); - expect(screen.getByTestId('test-id')).not.toHaveClass('open'); - expect(screen.getByRole('button')).toHaveAttribute( - 'aria-expanded', - 'false' - ); - - // when - userEvent.click(screen.getByRole('button')); - - // then - expect(screen.getByTestId('test-id')).toHaveClass('open'); - expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'true'); - - // when - userEvent.click(document.body); - - // then - expect(screen.getByTestId('test-id')).not.toHaveClass('open'); - expect(screen.getByRole('button')).toHaveAttribute( - 'aria-expanded', - 'false' - ); - }); - - it('closes when mousedown outside if rootCloseEvent set', () => { - // given - render( - - {dropdownChildren} - - ); - expect(screen.getByTestId('test-id')).not.toHaveClass('open'); - expect(screen.getByRole('button')).toHaveAttribute( - 'aria-expanded', - 'false' - ); - - // when - userEvent.click(screen.getByRole('button')); - - // then - expect(screen.getByTestId('test-id')).toHaveClass('open'); - expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'true'); - - // when - fireEvent.mouseDown(document.body); - - // then - expect(screen.getByTestId('test-id')).not.toHaveClass('open'); - expect(screen.getByRole('button')).toHaveAttribute( - 'aria-expanded', - 'false' - ); - }); - - it('opens if dropdown contains no focusable menu item', () => { - // given - render( - - Toggle - -
  • Some custom nonfocusable content
  • -
    -
    - ); - - // when - userEvent.click(screen.getByRole('button')); - - // then - expect(screen.getByTestId('dropdown')).toHaveClass('open'); - }); - - it('when focused and closed toggles open when the key "down" is pressed', () => { - // given - render(simpleDropdown); - - // when - fireEvent.keyDown(screen.getByRole('button'), { - key: 'ArrowDown', - code: 'ArrowDown', - keyCode: keycode('down'), - charCode: keycode('down'), - }); - - // then - expect(screen.getByTestId('test-id')).toHaveClass('open'); - expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'true'); - }); - - it('button has aria-haspopup attribute (As per W3C WAI-ARIA Spec)', () => { - // when - render(simpleDropdown); - - // then - expect(screen.getByRole('button')).toHaveAttribute('aria-haspopup', 'true'); - }); - - it('does not pass onSelect to DOM node', () => { - // given - const onSelect = jest.fn(); - render( - - {dropdownChildren} - - ); - expect(onSelect).not.toBeCalled(); - - // when - userEvent.click(screen.getByRole('button')); - userEvent.click(screen.getByRole('menuitem', { name: 'Item 4' })); - - // then - expect(onSelect).toBeCalled(); - }); - - it('closes when child MenuItem is selected', () => { - // given - render( - - {dropdownChildren} - - ); - expect(screen.getByTestId('test-id')).not.toHaveClass('open'); - expect(screen.getByRole('button')).toHaveAttribute( - 'aria-expanded', - 'false' - ); - - // when - userEvent.click(screen.getByRole('button')); - - // then - expect(screen.getByTestId('test-id')).toHaveClass('open'); - expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'true'); - - // when - userEvent.click(screen.getByRole('menuitem', { name: 'Item 4' })); - - // then - expect(screen.getByTestId('test-id')).not.toHaveClass('open'); - expect(screen.getByRole('button')).toHaveAttribute( - 'aria-expanded', - 'false' - ); - }); - - it('does not close when onToggle is controlled', () => { - // given - const handleSelect = jest.fn(); - render( - - {dropdownChildren} - - ); - - // when - userEvent.click(screen.getByRole('button')); - expect(screen.getByTestId('test-id')).toHaveClass('open'); - userEvent.click(screen.getByRole('menuitem', { name: 'Item 4' })); - - // then - expect(screen.getByTestId('test-id')).toHaveClass('open'); - }); - - it('is open with explicit prop', () => { - // given - function OpenProp() { - const [open, setOpen] = useState(false); - - return ( -
    - - {}} - title="Prop open control" - data-testid="test-id" - id="lol" - > - {dropdownChildren} - -
    - ); - } - - render(); - expect(screen.getByTestId('test-id')).not.toHaveClass('open'); - - // when - userEvent.click(screen.getByRole('button', { name: 'Outer button' })); - - // then - expect(screen.getByTestId('test-id')).toHaveClass('open'); - - // when - userEvent.click(screen.getByRole('button', { name: 'Outer button' })); - - // then - expect(screen.getByTestId('test-id')).not.toHaveClass('open'); - }); - - it('has aria-labelledby same id as ezz toggle button', () => { - // when - render(simpleDropdown); - - // then - const id = screen.getByRole('button').getAttribute('id'); - expect(screen.getByRole('menu')).toHaveAttribute('aria-labelledby', id); - }); - - describe('PropType validation', () => { - describe('children', () => { - const originalConsoleError = console.error; - - beforeEach(() => { - console.error = jest.fn(); - }); - - afterEach(() => { - console.error = originalConsoleError; - }); - - xit('menu is exclusive', () => { - // when - render( - - - - - - ); - - // then - expect(console.error.mock.calls[0]).toContain( - '(children) Dropdown - Duplicate children detected of bsRole: menu. Only one child each allowed with the following bsRoles: menu' - ); - }); - - xit('menu is required', () => { - // Dropdowns can't render without a menu. - render( - - - - ); - - // then - expect(console.error.mock.calls[0][0]).toContain( - 'Warning: Failed prop type: (children) Dropdown - Missing a required child with bsRole: menu. Dropdown must have at least one child of each of the following bsRoles: toggle, menu' - ); - }); - - xit('toggles are not exclusive', () => { - // when - render( - - - - - - ); - - // then - expect(console.error).not.toBeCalled(); - }); - - xit('toggle is required', () => { - // when - render( - - - - ); - - // then - expect(console.error.mock.calls[0]).toContain( - '(children) Dropdown - Missing a required child with bsRole: toggle. Dropdown must have at least one child of each of the following bsRoles: toggle, menu' - ); - }); - }); - }); - - describe('ref', () => { - const originalConsoleError = console.error; - - beforeEach(() => { - console.error = jest.fn(); - }); - - afterEach(() => { - console.error = originalConsoleError; - }); - - it('chains refs', () => { - // given - function RefDropdown() { - const [hasBaseRef, setHasBaseRef] = useState(false); - const [hasToggleRef, setHasToggleRef] = useState(false); - const [hasMenuRef, setHasMenuRef] = useState(false); - - const setBaseRef = () => { - setHasBaseRef(true); - }; - const setToggleRef = () => { - setHasToggleRef(true); - }; - const setMenuRef = () => { - setHasMenuRef(true); - }; - - return ( - <> - - - - - {hasBaseRef &&
    } - {hasToggleRef &&
    } - {hasMenuRef &&
    } - - ); - } - - // when - render(); - - // then - expect(screen.getByTestId('baseRefSet')).toBeInTheDocument(); - expect(screen.getByTestId('toggleRefSet')).toBeInTheDocument(); - expect(screen.getByTestId('menuRefSet')).toBeInTheDocument(); - }); - - xit('warns when a string ref is specified', () => { - // given - function RefDropdown() { - return ( - - - - - ); - } - - // when - render(); - - // then - expect(console.error.mock.calls[0][0]).toContain( - 'String refs are not supported' - ); - }); - }); - - describe('focusable state', () => { - let focusableContainer; - - beforeEach(() => { - focusableContainer = document.createElement('div'); - document.body.appendChild(focusableContainer); - }); - - afterEach(() => { - ReactDOM.unmountComponentAtNode(focusableContainer); - document.body.removeChild(focusableContainer); - }); - - it('when focused and closed sets focus on first menu item when the key "down" is pressed', () => { - // given - render(simpleDropdown, { container: focusableContainer }); - - // when - fireEvent.focus(screen.getByRole('button')); - fireEvent.keyDown(screen.getByRole('button'), { - key: 'ArrowDown', - keyCode: keycode('down'), - }); - - // then - expect(screen.getByRole('menuitem', { name: 'Item 1' })).toHaveFocus(); - }); - - it('when focused and open does not toggle closed when the key "down" is pressed', () => { - // given - render(simpleDropdown); - - // when - userEvent.click(screen.getByRole('button')); - fireEvent.keyDown(screen.getByRole('button'), { - key: 'ArrowDown', - keyCode: keycode('down'), - }); - - // then - expect(screen.getByRole('button')).toHaveAttribute( - 'aria-expanded', - 'true' - ); - expect(screen.getByTestId('test-id')).toHaveClass('open'); - }); - - // This test is more complicated then it appears to need. This is - // because there was an intermittent failure of the test when not structured this way - // The failure occurred when all tests in the suite were run together, but not a subset of the tests. - // - // I am fairly confident that the failure is due to a test specific conflict and not an actual bug. - it('when open and the key "esc" is pressed the menu is closed and focus is returned to the button', () => { - // given - render( - - {dropdownChildren} - , - { container: focusableContainer } - ); - const firstItem = screen.getByRole('menuitem', { name: 'Item 1' }); - expect(firstItem).toHaveFocus(); - - // when - fireEvent.keyDown(firstItem, { - key: 'Escape', - keyCode: keycode('esc'), - }); - - // then - expect(screen.getByRole('button')).toHaveFocus(); - expect(screen.getByTestId('test-id')).not.toHaveClass('open'); - }); - - it('when open and the key "tab" is pressed the menu is closed and focus is progress to the next focusable element', () => { - // given - render( - - {simpleDropdown} - - , - { attachTo: focusableContainer } - ); - - // when - userEvent.click(screen.getByRole('button')); - expect(screen.getByRole('button')).toHaveAttribute( - 'aria-expanded', - 'true' - ); - fireEvent.keyDown(screen.getByRole('button'), { - key: 'Tab', - keyCode: keycode('tab'), - }); - - // then - expect(screen.getByRole('button')).toHaveAttribute( - 'aria-expanded', - 'false' - ); - }); - }); - - describe('DOM event and source passed to onToggle', () => { - let focusableContainer; - - beforeEach(() => { - focusableContainer = document.createElement('div'); - document.body.appendChild(focusableContainer); - }); - - afterEach(() => { - ReactDOM.unmountComponentAtNode(focusableContainer); - document.body.removeChild(focusableContainer); - }); - - it('passes open, event, and source correctly when opened with click', () => { - // given - const onToggle = jest.fn(); - render( - - {dropdownChildren} - - ); - expect(onToggle).not.toHaveBeenCalled(); - - // when - userEvent.click(screen.getByRole('button')); - - // then - expect(onToggle).toHaveBeenCalledWith(true, expect.any(Object), { - source: 'click', - }); - }); - - it('passes open, event, and source correctly when closed with click', () => { - // given - const onToggle = jest.fn(); - render( - - {dropdownChildren} - - ); - expect(onToggle).not.toHaveBeenCalled(); - - // when - userEvent.click(screen.getByRole('button')); - expect(onToggle).toHaveBeenCalledTimes(1); - userEvent.click(screen.getByRole('button')); - - // then - expect(onToggle.mock.calls.length).toBeGreaterThanOrEqual(2); - expect(onToggle).toHaveBeenCalledWith(false, expect.any(Object), { - source: 'click', - }); - }); - - it('passes open, event, and source correctly when child selected', () => { - // given - const onToggle = jest.fn(); - render( - - Child Title - - Item 1 - - - ); - - // when - userEvent.click(screen.getByRole('button')); - expect(onToggle).toBeCalledTimes(1); - userEvent.click(screen.getByRole('menuitem', { name: 'Item 1' })); - - // then - expect(onToggle).toBeCalledTimes(2); - expect(onToggle).toHaveBeenLastCalledWith(false, expect.any(Object), { - source: 'select', - }); - }); - - it('passes open, event, and source correctly when opened with keydown', () => { - // given - const onToggle = jest.fn(); - render( - - {dropdownChildren} - - ); - - // when - fireEvent.keyDown(screen.getByRole('button'), { - key: 'ArrowDown', - keyCode: keycode('down'), - }); - - // then - expect(onToggle).toHaveBeenCalledTimes(1); - expect(onToggle).toHaveBeenCalledWith(true, expect.any(Object), { - source: 'keydown', - }); - }); - }); - - it('should derive bsClass from parent', () => { - // when - render( - - Child Title - - Item 1 - - - ); - - // then - expect(screen.getByRole('button')).toHaveClass('my-dropdown-toggle'); - expect(screen.getByRole('menu')).toHaveClass('my-dropdown-menu'); - }); + const dropdownChildren = [ + Child Title, + + Item 1 + Item 2 + Item 3 + Item 4 + , + ]; + + const simpleDropdown = ( + + {dropdownChildren} + + ); + + it('renders div with dropdown class', () => { + // when + render({dropdownChildren}); + + // then + const group = screen.getByRole('menu').parentElement; + expect(group.tagName).toBe('DIV'); + expect(group).toHaveClass('dropdown'); + expect(group).not.toHaveClass('dropup'); + }); + + it('renders div with dropup class', () => { + // when + render( + + {dropdownChildren} + , + ); + + // then + const group = screen.getByRole('menu').parentElement; + expect(group.tagName).toBe('DIV'); + expect(group).not.toHaveClass('dropdown'); + expect(group).toHaveClass('dropup'); + }); + + it('renders toggle with Dropdown.Toggle', () => { + // when + render(simpleDropdown); + + // then + const toggle = screen.getByRole('button', { name: 'Child Title' }); + expect(toggle.tagName).toBe('BUTTON'); + expect(toggle).toHaveClass('btn btn-default dropdown-toggle'); + expect(toggle).toHaveAttribute('type', 'button'); + expect(toggle).toHaveAttribute('aria-expanded', 'false'); + }); + + it('renders dropdown toggle button caret', () => { + // when + render(simpleDropdown); + + // then + const btn = screen.getByRole('button', { name: 'Child Title' }); + expect(btn.querySelector('span.caret')).toBeTruthy(); + }); + + it('does not render toggle button caret', () => { + // when + render(Child Text); + + // then + const caret = screen.getByRole('button', { name: 'Child Text' }); + expect(caret.querySelector('.caret')).toBeFalsy(); + }); + + it('renders custom menu', () => { + // when + render( + + Child Text + + + Item 1 + + , + ); + + // then + expect(screen.getByRole('menu')).toBeInTheDocument(); + expect(screen.getByRole('menu')).toHaveClass('custom-menu'); + }); + + it('forwards pullRight to menu', () => { + // when + render( + + {dropdownChildren} + , + ); + + // then + expect(screen.getByRole('menu')).toHaveClass('dropdown-menu-right'); + }); + + // NOTE: The onClick event handler is invoked for both the Enter and Space + // keys as well since the component is a button. I cannot figure out how to + // get ReactTestUtils to simulate such though. + it('toggles open/closed when clicked', async () => { + const user = userEvent.setup(); + + // given + render(simpleDropdown); + expect(screen.getByTestId('test-id')).not.toHaveClass('open'); + expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'false'); + + // when + await user.click(screen.getByRole('button')); + + // then + expect(screen.getByTestId('test-id')).toHaveClass('open'); + expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'true'); + + // when + await user.click(screen.getByRole('button')); + + // then + expect(screen.getByTestId('test-id')).not.toHaveClass('open'); + expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'false'); + }); + + it('closes when clicked outside', async () => { + const user = userEvent.setup(); + + // given + render(simpleDropdown); + expect(screen.getByTestId('test-id')).not.toHaveClass('open'); + expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'false'); + + // when + await user.click(screen.getByRole('button')); + + // then + expect(screen.getByTestId('test-id')).toHaveClass('open'); + expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'true'); + + // when + await user.click(document.body); + + // then + expect(screen.getByTestId('test-id')).not.toHaveClass('open'); + expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'false'); + }); + + it('closes when mousedown outside if rootCloseEvent set', async () => { + const user = userEvent.setup(); + + // given + render( + + {dropdownChildren} + , + ); + expect(screen.getByTestId('test-id')).not.toHaveClass('open'); + expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'false'); + + // when + await user.click(screen.getByRole('button')); + + // then + expect(screen.getByTestId('test-id')).toHaveClass('open'); + expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'true'); + + // when + fireEvent.mouseDown(document.body); + + // then + expect(screen.getByTestId('test-id')).not.toHaveClass('open'); + expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'false'); + }); + + it('opens if dropdown contains no focusable menu item', async () => { + const user = userEvent.setup(); + + // given + render( + + Toggle + +
  • Some custom nonfocusable content
  • +
    +
    , + ); + + // when + await user.click(screen.getByRole('button')); + + // then + expect(screen.getByTestId('dropdown')).toHaveClass('open'); + }); + + it('when focused and closed toggles open when the key "down" is pressed', async () => { + const user = userEvent.setup(); + + // given + render(simpleDropdown); + + // when + screen.getByRole('button').focus(); + await user.keyboard('[ArrowDown]'); + + // then + expect(screen.getByTestId('test-id')).toHaveClass('open'); + expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'true'); + }); + + it('button has aria-haspopup attribute (As per W3C WAI-ARIA Spec)', () => { + // when + render(simpleDropdown); + + // then + expect(screen.getByRole('button')).toHaveAttribute('aria-haspopup', 'true'); + }); + + it('does not pass onSelect to DOM node', async () => { + const user = userEvent.setup(); + + // given + const onSelect = jest.fn(); + render( + + {dropdownChildren} + , + ); + expect(onSelect).not.toHaveBeenCalled(); + + // when + await user.click(screen.getByRole('button')); + await user.click(screen.getByRole('menuitem', { name: 'Item 4' })); + + // then + expect(onSelect).toHaveBeenCalled(); + }); + + it('closes when child MenuItem is selected', async () => { + const user = userEvent.setup(); + + // given + render( + + {dropdownChildren} + , + ); + expect(screen.getByTestId('test-id')).not.toHaveClass('open'); + expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'false'); + + // when + await user.click(screen.getByRole('button')); + + // then + expect(screen.getByTestId('test-id')).toHaveClass('open'); + expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'true'); + + // when + await user.click(screen.getByRole('menuitem', { name: 'Item 4' })); + + // then + expect(screen.getByTestId('test-id')).not.toHaveClass('open'); + expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'false'); + }); + + it('does not close when onToggle is controlled', async () => { + const user = userEvent.setup(); + + // given + const handleSelect = jest.fn(); + render( + + {dropdownChildren} + , + ); + + // when + await user.click(screen.getByRole('button')); + expect(screen.getByTestId('test-id')).toHaveClass('open'); + await user.click(screen.getByRole('menuitem', { name: 'Item 4' })); + + // then + expect(screen.getByTestId('test-id')).toHaveClass('open'); + }); + + it('is open with explicit prop', async () => { + const user = userEvent.setup(); + + // given + function OpenProp() { + const [open, setOpen] = useState(false); + + return ( +
    + + {}} + title="Prop open control" + data-testid="test-id" + id="lol" + > + {dropdownChildren} + +
    + ); + } + + render(); + expect(screen.getByTestId('test-id')).not.toHaveClass('open'); + + // when + await user.click(screen.getByRole('button', { name: 'Outer button' })); + + // then + expect(screen.getByTestId('test-id')).toHaveClass('open'); + + // when + await user.click(screen.getByRole('button', { name: 'Outer button' })); + + // then + expect(screen.getByTestId('test-id')).not.toHaveClass('open'); + }); + + it('has aria-labelledby same id as ezz toggle button', () => { + // when + render(simpleDropdown); + + // then + const id = screen.getByRole('button').getAttribute('id'); + expect(screen.getByRole('menu')).toHaveAttribute('aria-labelledby', id); + }); + + describe('PropType validation', () => { + describe('children', () => { + const originalConsoleError = console.error; + + beforeEach(() => { + console.error = jest.fn(); + }); + + afterEach(() => { + console.error = originalConsoleError; + }); + + xit('menu is exclusive', () => { + // when + render( + + + + + , + ); + + // then + expect(console.error.mock.calls[0]).toContain( + '(children) Dropdown - Duplicate children detected of bsRole: menu. Only one child each allowed with the following bsRoles: menu', + ); + }); + + xit('menu is required', () => { + // Dropdowns can't render without a menu. + render( + + + , + ); + + // then + expect(console.error.mock.calls[0][0]).toContain( + 'Warning: Failed prop type: (children) Dropdown - Missing a required child with bsRole: menu. Dropdown must have at least one child of each of the following bsRoles: toggle, menu', + ); + }); + + xit('toggles are not exclusive', () => { + // when + render( + + + + + , + ); + + // then + expect(console.error).not.toHaveBeenCalled(); + }); + + xit('toggle is required', () => { + // when + render( + + + , + ); + + // then + expect(console.error.mock.calls[0]).toContain( + '(children) Dropdown - Missing a required child with bsRole: toggle. Dropdown must have at least one child of each of the following bsRoles: toggle, menu', + ); + }); + }); + }); + + describe('ref', () => { + const originalConsoleError = console.error; + + beforeEach(() => { + console.error = jest.fn(); + }); + + afterEach(() => { + console.error = originalConsoleError; + }); + + it('chains refs', () => { + // given + function RefDropdown() { + const [hasBaseRef, setHasBaseRef] = useState(false); + const [hasToggleRef, setHasToggleRef] = useState(false); + const [hasMenuRef, setHasMenuRef] = useState(false); + + const setBaseRef = () => { + setHasBaseRef(true); + }; + const setToggleRef = () => { + setHasToggleRef(true); + }; + const setMenuRef = () => { + setHasMenuRef(true); + }; + + return ( + <> + + + + + {hasBaseRef &&
    } + {hasToggleRef &&
    } + {hasMenuRef &&
    } + + ); + } + + // when + render(); + + // then + expect(screen.getByTestId('baseRefSet')).toBeInTheDocument(); + expect(screen.getByTestId('toggleRefSet')).toBeInTheDocument(); + expect(screen.getByTestId('menuRefSet')).toBeInTheDocument(); + }); + + xit('warns when a string ref is specified', () => { + // given + function RefDropdown() { + return ( + + + + + ); + } + + // when + render(); + + // then + expect(console.error.mock.calls[0][0]).toContain('String refs are not supported'); + }); + }); + + describe('focusable state', () => { + let focusableContainer; + + beforeEach(() => { + focusableContainer = document.createElement('div'); + document.body.appendChild(focusableContainer); + }); + + afterEach(() => { + ReactDOM.unmountComponentAtNode(focusableContainer); + document.body.removeChild(focusableContainer); + }); + + it('when focused and closed sets focus on first menu item when the key "down" is pressed', async () => { + const user = userEvent.setup(); + + // given + render(simpleDropdown, { container: focusableContainer }); + + // when + screen.getByRole('button').focus(); + await user.keyboard('[ArrowDown]'); + + // then + expect(screen.getByRole('menuitem', { name: 'Item 1' })).toHaveFocus(); + }); + + it('when focused and open does not toggle closed when the key "down" is pressed', async () => { + const user = userEvent.setup(); + + // given + render(simpleDropdown); + + // when + await user.click(screen.getByRole('button')); + await user.keyboard('[ArrowDown]'); + + // then + expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'true'); + expect(screen.getByTestId('test-id')).toHaveClass('open'); + }); + + // This test is more complicated then it appears to need. This is + // because there was an intermittent failure of the test when not structured this way + // The failure occurred when all tests in the suite were run together, but not a subset of the tests. + // + // I am fairly confident that the failure is due to a test specific conflict and not an actual bug. + it.only('when open and the key "esc" is pressed the menu is closed and focus is returned to the button', async () => { + const user = userEvent.setup(); + + // given + render( + + {dropdownChildren} + , + { container: focusableContainer }, + ); + const firstItem = screen.getByRole('menuitem', { name: 'Item 1' }); + expect(firstItem).toHaveFocus(); + + // when + await user.keyboard('[Escape]'); + + // then + expect(screen.getByRole('button')).toHaveFocus(); + expect(screen.getByTestId('test-id')).not.toHaveClass('open'); + }); + + it('when open and the key "tab" is pressed the menu is closed and focus is progress to the next focusable element', async () => { + const user = userEvent.setup(); + + // given + render( + + {simpleDropdown} + + , + { attachTo: focusableContainer }, + ); + + // when + await user.click(screen.getByRole('button')); + expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'true'); + await user.keyboard('[Tab]'); + + // then + expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'false'); + }); + }); + + describe('DOM event and source passed to onToggle', () => { + let focusableContainer; + + beforeEach(() => { + focusableContainer = document.createElement('div'); + document.body.appendChild(focusableContainer); + }); + + afterEach(() => { + ReactDOM.unmountComponentAtNode(focusableContainer); + document.body.removeChild(focusableContainer); + }); + + it('passes open, event, and source correctly when opened with click', async () => { + const user = userEvent.setup(); + + // given + const onToggle = jest.fn(); + render( + + {dropdownChildren} + , + ); + expect(onToggle).not.toHaveBeenCalled(); + + // when + await user.click(screen.getByRole('button')); + + // then + expect(onToggle).toHaveBeenCalledWith(true, expect.any(Object), { + source: 'click', + }); + }); + + it('passes open, event, and source correctly when closed with click', async () => { + const user = userEvent.setup(); + + // given + const onToggle = jest.fn(); + render( + + {dropdownChildren} + , + ); + expect(onToggle).not.toHaveBeenCalled(); + + // when + await user.click(screen.getByRole('button')); + expect(onToggle).toHaveBeenCalledTimes(1); + await user.click(screen.getByRole('button')); + + // then + expect(onToggle.mock.calls.length).toBeGreaterThanOrEqual(2); + expect(onToggle).toHaveBeenCalledWith(false, expect.any(Object), { + source: 'click', + }); + }); + + it('passes open, event, and source correctly when child selected', async () => { + const user = userEvent.setup(); + + // given + const onToggle = jest.fn(); + render( + + Child Title + + Item 1 + + , + ); + + // when + await user.click(screen.getByRole('button')); + expect(onToggle).toHaveBeenCalledTimes(1); + await user.click(screen.getByRole('menuitem', { name: 'Item 1' })); + + // then + expect(onToggle).toHaveBeenCalledTimes(2); + expect(onToggle).toHaveBeenLastCalledWith(false, expect.any(Object), { + source: 'select', + }); + }); + + it('passes open, event, and source correctly when opened with keydown', async () => { + const user = userEvent.setup(); + + // given + const onToggle = jest.fn(); + render( + + {dropdownChildren} + , + ); + + // when + screen.getByRole('button').focus(); + await user.keyboard('[ArrowDown]'); + + // then + expect(onToggle).toHaveBeenCalledTimes(1); + expect(onToggle).toHaveBeenCalledWith(true, expect.any(Object), { + source: 'keydown', + }); + }); + }); + + it('should derive bsClass from parent', () => { + // when + render( + + Child Title + + Item 1 + + , + ); + + // then + expect(screen.getByRole('button')).toHaveClass('my-dropdown-toggle'); + expect(screen.getByRole('menu')).toHaveClass('my-dropdown-menu'); + }); }); diff --git a/fork/react-bootstrap/src/DropdownMenu.js b/fork/react-bootstrap/src/DropdownMenu.js index 249346926e..8a7d631a60 100644 --- a/fork/react-bootstrap/src/DropdownMenu.js +++ b/fork/react-bootstrap/src/DropdownMenu.js @@ -1,143 +1,126 @@ -import classNames from 'classnames'; -import keycode from 'keycode'; import React from 'react'; -import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; import RootCloseWrapper from 'react-overlays/lib/RootCloseWrapper'; -import { - bsClass, - getClassSet, - prefix, - splitBsPropsAndOmit -} from './utils/bootstrapUtils'; +import classNames from 'classnames'; +import PropTypes from 'prop-types'; + +import { bsClass, getClassSet, prefix, splitBsPropsAndOmit } from './utils/bootstrapUtils'; import createChainedFunction from './utils/createChainedFunction'; import ValidComponentChildren from './utils/ValidComponentChildren'; const propTypes = { - open: PropTypes.bool, - pullRight: PropTypes.bool, - onClose: PropTypes.func, - labelledBy: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - onSelect: PropTypes.func, - rootCloseEvent: PropTypes.oneOf(['click', 'mousedown']) + open: PropTypes.bool, + pullRight: PropTypes.bool, + onClose: PropTypes.func, + labelledBy: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + onSelect: PropTypes.func, + rootCloseEvent: PropTypes.oneOf(['click', 'mousedown']), }; const defaultProps = { - bsRole: 'menu', - pullRight: false + bsRole: 'menu', + pullRight: false, }; class DropdownMenu extends React.Component { - constructor(props) { - super(props); - - this.handleRootClose = this.handleRootClose.bind(this); - this.handleKeyDown = this.handleKeyDown.bind(this); - } - - getFocusableMenuItems() { - const node = ReactDOM.findDOMNode(this); - if (!node) { - return []; - } - - return Array.from(node.querySelectorAll('[tabIndex="-1"]')); - } - - getItemsAndActiveIndex() { - const items = this.getFocusableMenuItems(); - const activeIndex = items.indexOf(document.activeElement); - - return { items, activeIndex }; - } - - focusNext() { - const { items, activeIndex } = this.getItemsAndActiveIndex(); - if (items.length === 0) { - return; - } - - const nextIndex = activeIndex === items.length - 1 ? 0 : activeIndex + 1; - items[nextIndex].focus(); - } - - focusPrevious() { - const { items, activeIndex } = this.getItemsAndActiveIndex(); - if (items.length === 0) { - return; - } - - const prevIndex = activeIndex === 0 ? items.length - 1 : activeIndex - 1; - items[prevIndex].focus(); - } - - handleKeyDown(event) { - switch (event.keyCode) { - case keycode.codes.down: - this.focusNext(); - event.preventDefault(); - break; - case keycode.codes.up: - this.focusPrevious(); - event.preventDefault(); - break; - case keycode.codes.esc: - case keycode.codes.tab: - this.props.onClose(event, { source: 'keydown' }); - break; - default: - } - } - - handleRootClose(event) { - this.props.onClose(event, { source: 'rootClose' }); - } - - render() { - const { - open, - pullRight, - labelledBy, - onSelect, - className, - rootCloseEvent, - children, - ...props - } = this.props; - - const [bsProps, elementProps] = splitBsPropsAndOmit(props, ['onClose']); - - const classes = { - ...getClassSet(bsProps), - [prefix(bsProps, 'right')]: pullRight - }; - - return ( - -
      - {ValidComponentChildren.map(children, child => - React.cloneElement(child, { - onKeyDown: createChainedFunction( - child.props.onKeyDown, - this.handleKeyDown - ), - onSelect: createChainedFunction(child.props.onSelect, onSelect) - }) - )} -
    -
    - ); - } + constructor(props) { + super(props); + + this.handleRootClose = this.handleRootClose.bind(this); + this.handleKeyDown = this.handleKeyDown.bind(this); + } + + getFocusableMenuItems() { + const node = ReactDOM.findDOMNode(this); + if (!node) { + return []; + } + + return Array.from(node.querySelectorAll('[tabIndex="-1"]')); + } + + getItemsAndActiveIndex() { + const items = this.getFocusableMenuItems(); + const activeIndex = items.indexOf(document.activeElement); + + return { items, activeIndex }; + } + + focusNext() { + const { items, activeIndex } = this.getItemsAndActiveIndex(); + if (items.length === 0) { + return; + } + + const nextIndex = activeIndex === items.length - 1 ? 0 : activeIndex + 1; + items[nextIndex].focus(); + } + + focusPrevious() { + const { items, activeIndex } = this.getItemsAndActiveIndex(); + if (items.length === 0) { + return; + } + + const prevIndex = activeIndex === 0 ? items.length - 1 : activeIndex - 1; + items[prevIndex].focus(); + } + + handleKeyDown(event) { + switch (event.key) { + case 'Down': + case 'ArrowDown': + this.focusNext(); + event.preventDefault(); + break; + case 'Up': + case 'ArrowUp': + this.focusPrevious(); + event.preventDefault(); + break; + case 'Esc': + case 'Escape': + case 'Tab': + this.props.onClose(event, { source: 'keydown' }); + break; + default: + } + } + + handleRootClose(event) { + this.props.onClose(event, { source: 'rootClose' }); + } + + render() { + const { open, pullRight, labelledBy, onSelect, className, rootCloseEvent, children, ...props } = + this.props; + + const [bsProps, elementProps] = splitBsPropsAndOmit(props, ['onClose']); + + const classes = { + ...getClassSet(bsProps), + [prefix(bsProps, 'right')]: pullRight, + }; + + return ( + +
      + {ValidComponentChildren.map(children, child => + React.cloneElement(child, { + onKeyDown: createChainedFunction(child.props.onKeyDown, this.handleKeyDown), + onSelect: createChainedFunction(child.props.onSelect, onSelect), + }), + )} +
    +
    + ); + } } DropdownMenu.propTypes = propTypes; diff --git a/fork/react-bootstrap/src/DropdownMenu.test.js b/fork/react-bootstrap/src/DropdownMenu.test.js index 5a5eb908e0..4f0c012ef7 100644 --- a/fork/react-bootstrap/src/DropdownMenu.test.js +++ b/fork/react-bootstrap/src/DropdownMenu.test.js @@ -1,7 +1,6 @@ -// import keycode from 'keycode'; // import ReactDOM from 'react-dom'; // import ReactTestUtils from 'react-dom/test-utils'; -import { screen, render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import DropdownMenu from './DropdownMenu'; import MenuItem from './MenuItem'; @@ -9,232 +8,232 @@ import MenuItem from './MenuItem'; // import { getOne } from './helpers'; describe('', () => { - const simpleMenu = ( - - Item 1 - Item 2 - Item 3 - Item 4 - - ); - - it('renders ul with dropdown-menu class', () => { - render(simpleMenu); - const node = screen.getByRole('menu'); - expect(node.tagName).toBe('UL'); - expect(node).toHaveClass('dropdown-menu'); - }); - - // xit('has role="menu"', () => { - // const instance = ReactTestUtils.renderIntoDocument(simpleMenu); - // const node = ReactDOM.findDOMNode(instance); - - // node.getAttribute('role').should.equal('menu'); - // }); - - // xit('has aria-labelledby=', () => { - // const instance1 = ReactTestUtils.renderIntoDocument( - // - // ); - // const instance2 = ReactTestUtils.renderIntoDocument( - // - // ); - // const node1 = ReactDOM.findDOMNode(instance1); - // const node2 = ReactDOM.findDOMNode(instance2); - - // node1.getAttribute('aria-labelledby').should.equal('herpa'); - // node2.getAttribute('aria-labelledby').should.equal('derpa'); - // }); - - // xit('forwards onSelect handler to MenuItems', (done) => { - // const selectedEvents = []; - // const onSelect = (eventKey) => { - // selectedEvents.push(eventKey); - - // if (selectedEvents.length === 4) { - // selectedEvents.should.eql(['1', '2', '3', '4']); - // done(); - // } - // }; - // const instance = ReactTestUtils.renderIntoDocument( - // - // Item 1 - // Item 2 - // Item 3 - // Item 4 - // - // ); - - // const menuItems = ReactTestUtils.scryRenderedDOMComponentsWithTag( - // instance, - // 'A' - // ); - - // menuItems.forEach((item) => { - // ReactTestUtils.Simulate.click(item); - // }); - // }); - - // xit('does not pass onSelect to DOM node', () => { - // shallow( {}} />) - // .find('ul') - // .props() - // .should.not.have.property('onSelect'); - // }); - - // xit('applies pull right', () => { - // const instance = ReactTestUtils.renderIntoDocument( - // - // Item - // - // ); - // const node = ReactDOM.findDOMNode(instance); - - // node.className.should.match(/\bdropdown-menu-right\b/); - // }); - - // xit('handles empty children', () => { - // ReactTestUtils.renderIntoDocument( - // - // Item - // {false && Item 2} - // - // ); - // }); - - // describe('focusable state', () => { - // let focusableContainer; - - // beforeEach(() => { - // focusableContainer = document.createElement('div'); - // document.body.appendChild(focusableContainer); - // }); - - // afterEach(() => { - // ReactDOM.unmountComponentAtNode(focusableContainer); - // document.body.removeChild(focusableContainer); - // }); - - // xit('clicking anything outside the menu will request close', () => { - // const requestClose = sinon.stub(); - // const instance = ReactDOM.render( - //
    - // - // - // Item - // - //
    , - // focusableContainer - // ); - - // const button = getOne(instance.getElementsByTagName('button')); - // button.click(); - - // requestClose.should.have.been.calledOnce; - // requestClose.getCall(0).args.length.should.equal(2); - // }); - - // describe('Keyboard Navigation', () => { - // xit('sets focus on next menu item when the key "down" is pressed', () => { - // const instance = ReactDOM.render(simpleMenu, focusableContainer); - - // const items = ReactTestUtils.scryRenderedDOMComponentsWithTag( - // instance, - // 'A' - // ); - // items.length.should.equal(4); - // items[0].focus(); - - // for (let i = 1; i < items.length; i++) { - // ReactTestUtils.Simulate.keyDown(document.activeElement, { - // keyCode: keycode('down'), - // }); - // document.activeElement.should.equal(items[i]); - // } - // }); - - // xit('with last item is focused when the key "down" is pressed first item gains focus', () => { - // const instance = ReactDOM.render(simpleMenu, focusableContainer); - - // const items = ReactTestUtils.scryRenderedDOMComponentsWithTag( - // instance, - // 'A' - // ); - // items.length.should.equal(4); - // items[3].focus(); - - // ReactTestUtils.Simulate.keyDown(document.activeElement, { - // keyCode: keycode('down'), - // }); - // document.activeElement.should.equal(items[0]); - // }); - - // xit('sets focus on previous menu item when the key "up" is pressed', () => { - // const instance = ReactDOM.render(simpleMenu, focusableContainer); - - // const items = ReactTestUtils.scryRenderedDOMComponentsWithTag( - // instance, - // 'A' - // ); - // items.length.should.equal(4); - // items[3].focus(); - - // for (let i = 2; i >= 0; i--) { - // ReactTestUtils.Simulate.keyDown(document.activeElement, { - // keyCode: keycode('up'), - // }); - // document.activeElement.should.equal(items[i]); - // } - // }); - - // xit('with first item focused when the key "up" is pressed last item gains focus', () => { - // const instance = ReactDOM.render(simpleMenu, focusableContainer); - - // const items = ReactTestUtils.scryRenderedDOMComponentsWithTag( - // instance, - // 'A' - // ); - // items.length.should.equal(4); - // items[0].focus(); - - // ReactTestUtils.Simulate.keyDown(document.activeElement, { - // keyCode: keycode('up'), - // }); - // document.activeElement.should.equal(items[3]); - // }); - - // ['esc', 'tab'].forEach((key) => { - // xit(`when the key "${key}" is pressed the requestClose prop is invoked with the originating event`, () => { - // const requestClose = sinon.spy(); - // const instance = ReactDOM.render( - // - // Item - // , - // focusableContainer - // ); - - // const item = ReactTestUtils.findRenderedDOMComponentWithTag( - // instance, - // 'A' - // ); - - // ReactTestUtils.Simulate.keyDown(item, { keyCode: keycode(key) }); - - // requestClose.should.have.been.calledOnce; - // requestClose.getCall(0).args[0].keyCode.should.equal(keycode(key)); - // }); - // }); - // }); - // }); - - // xit('Should pass props to dropdown', () => { - // let instance = ReactTestUtils.renderIntoDocument( - // - // MenuItem 1 content - // - // ); - - // let node = ReactDOM.findDOMNode(instance); - // assert.ok(node.className.match(/\bnew-fancy-class\b/)); - // }); + const simpleMenu = ( + + Item 1 + Item 2 + Item 3 + Item 4 + + ); + + it('renders ul with dropdown-menu class', () => { + render(simpleMenu); + const node = screen.getByRole('menu'); + expect(node.tagName).toBe('UL'); + expect(node).toHaveClass('dropdown-menu'); + }); + + // xit('has role="menu"', () => { + // const instance = ReactTestUtils.renderIntoDocument(simpleMenu); + // const node = ReactDOM.findDOMNode(instance); + + // node.getAttribute('role').should.equal('menu'); + // }); + + // xit('has aria-labelledby=', () => { + // const instance1 = ReactTestUtils.renderIntoDocument( + // + // ); + // const instance2 = ReactTestUtils.renderIntoDocument( + // + // ); + // const node1 = ReactDOM.findDOMNode(instance1); + // const node2 = ReactDOM.findDOMNode(instance2); + + // node1.getAttribute('aria-labelledby').should.equal('herpa'); + // node2.getAttribute('aria-labelledby').should.equal('derpa'); + // }); + + // xit('forwards onSelect handler to MenuItems', (done) => { + // const selectedEvents = []; + // const onSelect = (eventKey) => { + // selectedEvents.push(eventKey); + + // if (selectedEvents.length === 4) { + // selectedEvents.should.eql(['1', '2', '3', '4']); + // done(); + // } + // }; + // const instance = ReactTestUtils.renderIntoDocument( + // + // Item 1 + // Item 2 + // Item 3 + // Item 4 + // + // ); + + // const menuItems = ReactTestUtils.scryRenderedDOMComponentsWithTag( + // instance, + // 'A' + // ); + + // menuItems.forEach((item) => { + // ReactTestUtils.Simulate.click(item); + // }); + // }); + + // xit('does not pass onSelect to DOM node', () => { + // shallow( {}} />) + // .find('ul') + // .props() + // .should.not.have.property('onSelect'); + // }); + + // xit('applies pull right', () => { + // const instance = ReactTestUtils.renderIntoDocument( + // + // Item + // + // ); + // const node = ReactDOM.findDOMNode(instance); + + // node.className.should.match(/\bdropdown-menu-right\b/); + // }); + + // xit('handles empty children', () => { + // ReactTestUtils.renderIntoDocument( + // + // Item + // {false && Item 2} + // + // ); + // }); + + // describe('focusable state', () => { + // let focusableContainer; + + // beforeEach(() => { + // focusableContainer = document.createElement('div'); + // document.body.appendChild(focusableContainer); + // }); + + // afterEach(() => { + // ReactDOM.unmountComponentAtNode(focusableContainer); + // document.body.removeChild(focusableContainer); + // }); + + // xit('clicking anything outside the menu will request close', () => { + // const requestClose = sinon.stub(); + // const instance = ReactDOM.render( + //
    + // + // + // Item + // + //
    , + // focusableContainer + // ); + + // const button = getOne(instance.getElementsByTagName('button')); + // button.click(); + + // requestClose.should.have.been.calledOnce; + // requestClose.getCall(0).args.length.should.equal(2); + // }); + + // describe('Keyboard Navigation', () => { + // xit('sets focus on next menu item when the key "down" is pressed', () => { + // const instance = ReactDOM.render(simpleMenu, focusableContainer); + + // const items = ReactTestUtils.scryRenderedDOMComponentsWithTag( + // instance, + // 'A' + // ); + // items.length.should.equal(4); + // items[0].focus(); + + // for (let i = 1; i < items.length; i++) { + // ReactTestUtils.Simulate.keyDown(document.activeElement, { + // keyCode: keycode('down'), + // }); + // document.activeElement.should.equal(items[i]); + // } + // }); + + // xit('with last item is focused when the key "down" is pressed first item gains focus', () => { + // const instance = ReactDOM.render(simpleMenu, focusableContainer); + + // const items = ReactTestUtils.scryRenderedDOMComponentsWithTag( + // instance, + // 'A' + // ); + // items.length.should.equal(4); + // items[3].focus(); + + // ReactTestUtils.Simulate.keyDown(document.activeElement, { + // keyCode: keycode('down'), + // }); + // document.activeElement.should.equal(items[0]); + // }); + + // xit('sets focus on previous menu item when the key "up" is pressed', () => { + // const instance = ReactDOM.render(simpleMenu, focusableContainer); + + // const items = ReactTestUtils.scryRenderedDOMComponentsWithTag( + // instance, + // 'A' + // ); + // items.length.should.equal(4); + // items[3].focus(); + + // for (let i = 2; i >= 0; i--) { + // ReactTestUtils.Simulate.keyDown(document.activeElement, { + // keyCode: keycode('up'), + // }); + // document.activeElement.should.equal(items[i]); + // } + // }); + + // xit('with first item focused when the key "up" is pressed last item gains focus', () => { + // const instance = ReactDOM.render(simpleMenu, focusableContainer); + + // const items = ReactTestUtils.scryRenderedDOMComponentsWithTag( + // instance, + // 'A' + // ); + // items.length.should.equal(4); + // items[0].focus(); + + // ReactTestUtils.Simulate.keyDown(document.activeElement, { + // keyCode: keycode('up'), + // }); + // document.activeElement.should.equal(items[3]); + // }); + + // ['esc', 'tab'].forEach((key) => { + // xit(`when the key "${key}" is pressed the requestClose prop is invoked with the originating event`, () => { + // const requestClose = sinon.spy(); + // const instance = ReactDOM.render( + // + // Item + // , + // focusableContainer + // ); + + // const item = ReactTestUtils.findRenderedDOMComponentWithTag( + // instance, + // 'A' + // ); + + // ReactTestUtils.Simulate.keyDown(item, { keyCode: keycode(key) }); + + // requestClose.should.have.been.calledOnce; + // requestClose.getCall(0).args[0].keyCode.should.equal(keycode(key)); + // }); + // }); + // }); + // }); + + // xit('Should pass props to dropdown', () => { + // let instance = ReactTestUtils.renderIntoDocument( + // + // MenuItem 1 content + // + // ); + + // let node = ReactDOM.findDOMNode(instance); + // assert.ok(node.className.match(/\bnew-fancy-class\b/)); + // }); }); diff --git a/fork/react-bootstrap/src/Nav.js b/fork/react-bootstrap/src/Nav.js index 9aa6123e63..b98dd600c1 100644 --- a/fork/react-bootstrap/src/Nav.js +++ b/fork/react-bootstrap/src/Nav.js @@ -1,18 +1,12 @@ -import classNames from 'classnames'; -import keycode from 'keycode'; import React, { cloneElement } from 'react'; -import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; + +import classNames from 'classnames'; +import PropTypes from 'prop-types'; import all from 'prop-types-extra/lib/all'; import warning from 'warning'; -import { - bsClass, - bsStyles, - getClassSet, - prefix, - splitBsProps -} from './utils/bootstrapUtils'; +import { bsClass, bsStyles, getClassSet, prefix, splitBsProps } from './utils/bootstrapUtils'; import createChainedFunction from './utils/createChainedFunction'; import ValidComponentChildren from './utils/ValidComponentChildren'; @@ -24,333 +18,324 @@ import ValidComponentChildren from './utils/ValidComponentChildren'; // Consider renaming or replacing them. const propTypes = { - /** - * Marks the NavItem with a matching `eventKey` as active. Has a - * higher precedence over `activeHref`. - */ - activeKey: PropTypes.any, - - /** - * Marks the child NavItem with a matching `href` prop as active. - */ - activeHref: PropTypes.string, - - /** - * NavItems are be positioned vertically. - */ - stacked: PropTypes.bool, - - justified: all( - PropTypes.bool, - ({ justified, navbar }) => - justified && navbar - ? Error('justified navbar `Nav`s are not supported') - : null - ), - - /** - * A callback fired when a NavItem is selected. - * - * ```js - * function ( - * Any eventKey, - * SyntheticEvent event? - * ) - * ``` - */ - onSelect: PropTypes.func, - - /** - * ARIA role for the Nav, in the context of a TabContainer, the default will - * be set to "tablist", but can be overridden by the Nav when set explicitly. - * - * When the role is set to "tablist" NavItem focus is managed according to - * the ARIA authoring practices for tabs: - * https://www.w3.org/TR/2013/WD-wai-aria-practices-20130307/#tabpanel - */ - role: PropTypes.string, - - /** - * Apply styling an alignment for use in a Navbar. This prop will be set - * automatically when the Nav is used inside a Navbar. - */ - navbar: PropTypes.bool, - - /** - * Float the Nav to the right. When `navbar` is `true` the appropriate - * contextual classes are added as well. - */ - pullRight: PropTypes.bool, - - /** - * Float the Nav to the left. When `navbar` is `true` the appropriate - * contextual classes are added as well. - */ - pullLeft: PropTypes.bool + /** + * Marks the NavItem with a matching `eventKey` as active. Has a + * higher precedence over `activeHref`. + */ + activeKey: PropTypes.any, + + /** + * Marks the child NavItem with a matching `href` prop as active. + */ + activeHref: PropTypes.string, + + /** + * Chidlren. + */ + children: PropTypes.node, + + /** + * Css classname. + */ + className: PropTypes.string, + + /** + * NavItems are be positioned vertically. + */ + stacked: PropTypes.bool, + + justified: all(PropTypes.bool, ({ justified, navbar }) => + justified && navbar ? Error('justified navbar `Nav`s are not supported') : null, + ), + + /** + * A callback fired when a NavItem is selected. + * + * ```js + * function ( + * Any eventKey, + * SyntheticEvent event? + * ) + * ``` + */ + onSelect: PropTypes.func, + + /** + * ARIA role for the Nav, in the context of a TabContainer, the default will + * be set to "tablist", but can be overridden by the Nav when set explicitly. + * + * When the role is set to "tablist" NavItem focus is managed according to + * the ARIA authoring practices for tabs: + * https://www.w3.org/TR/2013/WD-wai-aria-practices-20130307/#tabpanel + */ + role: PropTypes.string, + + /** + * Apply styling an alignment for use in a Navbar. This prop will be set + * automatically when the Nav is used inside a Navbar. + */ + navbar: PropTypes.bool, + + /** + * Float the Nav to the right. When `navbar` is `true` the appropriate + * contextual classes are added as well. + */ + pullRight: PropTypes.bool, + + /** + * Float the Nav to the left. When `navbar` is `true` the appropriate + * contextual classes are added as well. + */ + pullLeft: PropTypes.bool, }; const defaultProps = { - justified: false, - pullRight: false, - pullLeft: false, - stacked: false + justified: false, + pullRight: false, + pullLeft: false, + stacked: false, }; const contextTypes = { - $bs_navbar: PropTypes.shape({ - bsClass: PropTypes.string, - onSelect: PropTypes.func - }), - - $bs_tabContainer: PropTypes.shape({ - activeKey: PropTypes.any, - onSelect: PropTypes.func.isRequired, - getTabId: PropTypes.func.isRequired, - getPaneId: PropTypes.func.isRequired - }) + $bs_navbar: PropTypes.shape({ + bsClass: PropTypes.string, + onSelect: PropTypes.func, + }), + + $bs_tabContainer: PropTypes.shape({ + activeKey: PropTypes.any, + onSelect: PropTypes.func.isRequired, + getTabId: PropTypes.func.isRequired, + getPaneId: PropTypes.func.isRequired, + }), }; class Nav extends React.Component { - componentDidUpdate() { - if (!this._needsRefocus) { - return; - } - - this._needsRefocus = false; - - const { children } = this.props; - const { activeKey, activeHref } = this.getActiveProps(); - - const activeChild = ValidComponentChildren.find(children, child => - this.isActive(child, activeKey, activeHref) - ); - - const childrenArray = ValidComponentChildren.toArray(children); - const activeChildIndex = childrenArray.indexOf(activeChild); - - const childNodes = ReactDOM.findDOMNode(this).children; - const activeNode = childNodes && childNodes[activeChildIndex]; - - if (!activeNode || !activeNode.firstChild) { - return; - } - - activeNode.firstChild.focus(); - } - - getActiveProps() { - const tabContainer = this.context.$bs_tabContainer; - - if (tabContainer) { - warning( - this.props.activeKey == null && !this.props.activeHref, - 'Specifying a `
    ); - -export const Combinations = withPropsCombinations(ActionButton, { - label: ['Click me'], - bsStyle: [ - 'default', - 'primary', - 'success', - 'info', - 'warning', - 'danger', - 'link', - 'info btn-inverse', - ], - icon: ['talend-dataprep'], - 'data-feature': ['my.feature'], - onClick: [action('You clicked me')], - hideLabel: [false, true], - inProgress: [true, false], - disabled: [false, true], - tooltip: [true], - tooltipLabel: ['Tooltip custom label'], -}); diff --git a/packages/components/src/Actions/ActionButton/Button.stories.module.scss b/packages/components/src/Actions/ActionButton/Button.stories.module.scss index d7d990e47d..3fd0c0f108 100644 --- a/packages/components/src/Actions/ActionButton/Button.stories.module.scss +++ b/packages/components/src/Actions/ActionButton/Button.stories.module.scss @@ -1,4 +1,4 @@ -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *; +@use '@talend/bootstrap-theme/src/theme/guidelines' as *; .storybook-wrapped-action { margin-top: 200px; diff --git a/packages/components/src/Actions/ActionDropdown/ActionDropdown.module.scss b/packages/components/src/Actions/ActionDropdown/ActionDropdown.module.scss index 35f21d9c78..667e751462 100644 --- a/packages/components/src/Actions/ActionDropdown/ActionDropdown.module.scss +++ b/packages/components/src/Actions/ActionDropdown/ActionDropdown.module.scss @@ -1,8 +1,8 @@ -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *; -@use '~@talend/design-tokens/lib/tokens'; +@use '@talend/bootstrap-theme/src/theme/guidelines' as *; +@use '@talend/design-tokens/lib/tokens' as tokens; $tc-dropdown-loader-padding: $padding-small !default; -$tc-dropdown-button-right-padding: 0.8rem; +$tc-dropdown-button-right-padding: 0.5rem; .tc-dropdown { &-button:global(.btn-link) { @@ -17,8 +17,8 @@ $tc-dropdown-button-right-padding: 0.8rem; padding-right: $tc-dropdown-button-right-padding; .tc-dropdown-caret { - width: 0.8rem; - height: 0.8rem; + width: 0.5rem; + height: 0.5rem; transition: transform 0.1s ease-in; will-change: transform; @@ -42,6 +42,11 @@ $tc-dropdown-button-right-padding: 0.8rem; } &-item { + a img { + max-width: initial; + } + + a img, a svg { margin: 0 $padding-smaller; } diff --git a/packages/components/src/Actions/ActionDropdown/ActionDropdown.test.js b/packages/components/src/Actions/ActionDropdown/ActionDropdown.test.js index d89f485472..0b16d58bb7 100644 --- a/packages/components/src/Actions/ActionDropdown/ActionDropdown.test.js +++ b/packages/components/src/Actions/ActionDropdown/ActionDropdown.test.js @@ -1,8 +1,10 @@ /* eslint-disable react/prop-types */ + /* eslint-disable react/display-name */ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import ActionDropdown, { InjectDropdownMenuItem, getMenuItem } from './ActionDropdown.component'; + +import ActionDropdown, { getMenuItem, InjectDropdownMenuItem } from './ActionDropdown.component'; jest.unmock('@talend/design-system'); @@ -17,7 +19,9 @@ function getComponent(key) { } describe('ActionDropdown', () => { - it('should call onToggle callback when click on trigger', () => { + it('should call onToggle callback when click on trigger', async () => { + const user = userEvent.setup(); + // given const onToggle = jest.fn(); const props = { @@ -34,19 +38,21 @@ describe('ActionDropdown', () => { const dropdownButton = screen.getByRole('button'); // when - userEvent.click(dropdownButton); + await user.click(dropdownButton); // then - expect(onToggle).toBeCalledWith(true); + expect(onToggle).toHaveBeenCalledWith(true); // when - userEvent.click(dropdownButton); + await user.click(dropdownButton); // then - expect(onToggle).toBeCalledWith(false); + expect(onToggle).toHaveBeenCalledWith(false); }); - it('should call onSelect callback when click on item', () => { + it('should call onSelect callback when click on item', async () => { + const user = userEvent.setup(); + // given const onSelectClick = jest.fn(); const onItemClick = jest.fn(); @@ -62,10 +68,10 @@ describe('ActionDropdown', () => { render(); // when - userEvent.click(screen.getByRole('menuitem', { name: 'Item 1' })); + await user.click(screen.getByRole('menuitem', { name: 'Item 1' })); // then - expect(onSelectClick).toBeCalledWith(expect.anything(), props.items[0]); + expect(onSelectClick).toHaveBeenCalledWith(expect.anything(), props.items[0]); expect(onItemClick.mock.calls[0][1]).toEqual({ action: { id: 'item1', label: 'Item 1' }, model: 'model', @@ -73,10 +79,10 @@ describe('ActionDropdown', () => { expect(onItemClick.mock.calls[0][0].type).toBe('click'); // when - userEvent.click(screen.getByRole('menuitem', { name: 'Item 2' })); + await user.click(screen.getByRole('menuitem', { name: 'Item 2' })); // then - expect(onSelectClick).toBeCalledWith(expect.anything(), props.items[1]); + expect(onSelectClick).toHaveBeenCalledWith(expect.anything(), props.items[1]); expect(onItemClick.mock.calls[1][1]).toEqual({ action: { id: 'item2', label: 'Item 2' }, model: 'model', @@ -162,7 +168,14 @@ describe('InjectDropdownMenuItem', () => { }); describe('Dropup', () => { - function testSwitch({ containerPosition, menuPosition, isInitialDropup, isDropupExpected }) { + async function testSwitch({ + containerPosition, + menuPosition, + isInitialDropup, + isDropupExpected, + }) { + const user = userEvent.setup(); + // given const { container } = render(
    @@ -180,7 +193,7 @@ describe('Dropup', () => { container.querySelector('.dropdown-menu').getBoundingClientRect = () => menuPosition; // when - userEvent.click(screen.getByRole('button')); + await user.click(screen.getByRole('button')); // then if (!isDropupExpected) { diff --git a/packages/components/src/Actions/ActionDropdown/Dropdown.stories.js b/packages/components/src/Actions/ActionDropdown/Dropdown.stories.js index 8820a9665a..03daf402c0 100644 --- a/packages/components/src/Actions/ActionDropdown/Dropdown.stories.js +++ b/packages/components/src/Actions/ActionDropdown/Dropdown.stories.js @@ -1,9 +1,9 @@ -import Immutable from 'immutable'; import { action } from '@storybook/addon-actions'; +import Immutable from 'immutable'; -import ActionDropdown from './ActionDropdown.component'; import FilterBar from '../../FilterBar'; import Action from '../Action'; +import ActionDropdown from './ActionDropdown.component'; const myAction = { id: 'context-dropdown-related-items', @@ -17,6 +17,12 @@ const myAction = { 'data-feature': 'actiondropdown.items', onClick: action('document 1 click'), }, + { + icon: 'src-data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+CiAgPGc+CiAgICA8cGF0aCBjbGFzcz0idGktYmFja2dyb3VuZCIgIGZpbGw9IiNGRjZFNzAiIGQ9Ik0xNiw4IEMxNiwxMi40MTg4NzM3IDEyLjQxODI2NDgsMTYuMDAwNjA4OCA4LDE2LjAwMDYwODggQzMuNTgxNzM1MTYsMTYuMDAwNjA4OCAwLDEyLjQxODg3MzcgMCw4IEMwLDMuNTgxNzM1MTYgMy41ODE3MzUxNiw1LjM0OTk2ODgzZS0xNCA4LDUuMzQ5OTY4ODNlLTE0IEMxMi40MTgyNjQ4LDUuMzQ5OTY4ODNlLTE0IDE2LDMuNTgxNzM1MTYgMTYsOCIvPgogICAgPHBhdGggY2xhc3M9InRpLWZvcmVncm91bmQiIGZpbGw9IiNGRkYiIGQ9Ik0xMC4xNzgwMTY4LDExLjQzMTYwMzQgQzEwLjE1MTM5NSwxMS4zNTQxNTggMTAuMTEzODgyNCwxMS4yODIxNTggMTAuMDY0ODczOSwxMS4yMTYyMDg0IEMxMC4wMTY0NzA2LDExLjE0OTY1MzggOS45Nzg5NTc5OCwxMS4xMjMwMzE5IDkuOTUyMzM2MTMsMTEuMTM2MzQyOSBDOS43ODQxMzQ0NSwxMS4yMTk4Mzg3IDkuNjAwMjAxNjgsMTEuMjkxMjMzNiA5LjQwMTE0Mjg2LDExLjM0ODcxMjYgQzkuMjAyMDg0MDMsMTEuNDA2MTkxNiA5LjAyOTA0MjAyLDExLjQzNTIzMzYgOC44ODMyMjY4OSwxMS40MzUyMzM2IEM4LjcxOTI2MDUsMTEuNDM1MjMzNiA4LjU5NDAxNjgxLDExLjM5NzcyMSA4LjUwODEwMDg0LDExLjMyNTcyMSBDOC40MjE1Nzk4MywxMS4yNTI1MTA5IDguMzU5ODY1NTUsMTEuMTE2OTgxNSA4LjMyMjM1Mjk0LDEwLjkyMDM0MjkgQzguMjg0MjM1MjksMTAuNzIzNzA0MiA4LjI2NTQ3ODk5LDEwLjQzNjMwOTIgOC4yNjU0Nzg5OSwxMC4wNTk5NzMxIEw4LjI2NTQ3ODk5LDYuMjQyNzYzMDMgTDkuNjkzMzc4MTUsNi4yNDI3NjMwMyBDOS43NDE3ODE1MSw2LjI0Mjc2MzAzIDkuNzg3MTU5NjYsNi4xOTYxNzQ3OSA5LjgyOTUxMjYxLDYuMTAyMzkzMjggQzkuODcxMjYwNSw2LjAxMDQyNjg5IDkuODkyNDM2OTcsNS44OTU0Njg5MSA5Ljg5MjQzNjk3LDUuNzU4MTI0MzcgQzkuODkyNDM2OTcsNS43MDAwNDAzNCA5Ljg3NzkxNTk3LDUuNjQ4NjExNzYgOS44NDg4NzM5NSw1LjYwNTA0ODc0IEM5LjgyMDQzNjk3LDUuNTYwODgwNjcgOS43ODE3MTQyOSw1LjUzNjY3ODk5IDkuNzMzMzEwOTIsNS41MzI0NDM3IEw4LjI2NTQ3ODk5LDUuNTMyNDQzNyBMOC4yNjU0Nzg5OSwzLjY3OTIgQzguMjY1NDc4OTksMy42MDIzNTk2NiA4LjIxNDY1NTQ2LDMuNTQ3OTA1ODggOC4xMjQ1MDQyLDMuNTEwOTk4MzIgQzcuOTU4MTE3NjUsMy40NTg5NjQ3MSA3Ljg2MDcwNTg4LDMuNjM5ODcyMjcgNy44NjA3MDU4OCwzLjYzOTg3MjI3IEM3LjQxNDE4NDg3LDQuNjcyMDczOTUgNi41Mzc0Nzg5OSw1LjQ2ODkxNDI5IDUuNDUzMjQzNyw1LjgwODM0Mjg2IEM1LjQ1MzI0MzcsNS44MDgzNDI4NiA1LjIzMTc5ODMyLDUuODYwOTgxNTEgNS4yODg2NzIyNyw2LjA5MjcxMjYxIEM1LjI5NTMyNzczLDYuMTEyNjc4OTkgNS4zMDEzNzgxNSw2LjEzMjY0NTM4IDUuMzExMDU4ODIsNi4xNTI2MTE3NiBDNS4zNDE5MTU5Nyw2LjIxNzM1MTI2IDUuMzc5NDI4NTcsNi4yNTA2Mjg1NyA1LjQyMzU5NjY0LDYuMjU2MDczOTUgTDYuMTMzOTE1OTcsNi4yNTYwNzM5NSBMNi4xMzM5MTU5NywxMC42NTE3MDQyIEM2LjEzMzkxNTk3LDExLjI3NTUwMjUgNi4zMjMyOTQxMiwxMS43MTE3Mzc4IDYuNzAyMDUwNDIsMTEuOTU5ODA1IEM3LjA4MDIwMTY4LDEyLjIwNzI2NzIgNy41ODg0MzY5NywxMi4zMzA2OTU4IDguMjI1NTQ2MjIsMTIuMzMwNjk1OCBDOC40MjUyMTAwOCwxMi4zMzA2OTU4IDguNjU2MzM2MTMsMTIuMjkzNzg4MiA4LjkxOTUyOTQxLDEyLjIxODc2MyBDOS4xODI3MjI2OSwxMi4xNDMxMzI4IDkuNDMwNzg5OTIsMTIuMDQ2OTMxMSA5LjY2MzEyNjA1LDExLjkyOTU1MjkgQzkuODk2MDY3MjMsMTEuODEyNzc5OCAxMC4wNjcyOTQxLDExLjY5NDE5MTYgMTAuMTc4MDE2OCwxMS41NzM3ODgyIEMxMC4yMDQ2Mzg3LDExLjU1Njg0NzEgMTAuMjA0NjM4NywxMS41MDkwNDg3IDEwLjE3ODAxNjgsMTEuNDMxNjAzNCIvPgogIDwvZz4KPC9zdmc+Cg==', + label: 'Button with icon as image', + onClick: action('Button with icon clicked'), + type: 'button', + }, { divider: true, }, diff --git a/packages/components/src/Actions/ActionFile/ActionFile.module.scss b/packages/components/src/Actions/ActionFile/ActionFile.module.scss index 80648e99e5..dd69b9957e 100644 --- a/packages/components/src/Actions/ActionFile/ActionFile.module.scss +++ b/packages/components/src/Actions/ActionFile/ActionFile.module.scss @@ -1,4 +1,4 @@ -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *; +@use '@talend/bootstrap-theme/src/theme/guidelines' as *; input[type='file'] { &.action-file-input:focus + label { diff --git a/packages/components/src/Actions/ActionIconToggle/ActionIconToggle.component.test.js b/packages/components/src/Actions/ActionIconToggle/ActionIconToggle.component.test.js index cc8ffe4de3..dca2582b57 100644 --- a/packages/components/src/Actions/ActionIconToggle/ActionIconToggle.component.test.js +++ b/packages/components/src/Actions/ActionIconToggle/ActionIconToggle.component.test.js @@ -1,5 +1,6 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; + import ActionIconToggle from './ActionIconToggle.component'; jest.unmock('@talend/design-system'); @@ -33,15 +34,17 @@ describe('ActionIconToggle', () => { expect(screen.getByRole('button')).toHaveClass('active'); }); - it('should call click callback', () => { + it('should call click callback', async () => { + const user = userEvent.setup(); + // given render(); - expect(inactiveIconToggle.onClick).not.toBeCalled(); + expect(inactiveIconToggle.onClick).not.toHaveBeenCalled(); // when - userEvent.click(screen.getByRole('button')); + await user.click(screen.getByRole('button')); // then - expect(inactiveIconToggle.onClick).toBeCalled(); + expect(inactiveIconToggle.onClick).toHaveBeenCalled(); }); }); diff --git a/packages/components/src/Actions/ActionIconToggle/ActionIconToggle.module.scss b/packages/components/src/Actions/ActionIconToggle/ActionIconToggle.module.scss index 7a11d2c012..7e21b53751 100644 --- a/packages/components/src/Actions/ActionIconToggle/ActionIconToggle.module.scss +++ b/packages/components/src/Actions/ActionIconToggle/ActionIconToggle.module.scss @@ -1,7 +1,7 @@ -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *; -@use '~@talend/design-tokens/lib/tokens'; +@use '@talend/bootstrap-theme/src/theme/guidelines' as *; +@use '@talend/design-tokens/lib/tokens' as tokens; -$tc-icon-toggle-size: 2.4rem !default; +$tc-icon-toggle-size: 1.5rem !default; $tc-icon-toggle-icon-size: $svg-sm-size !default; $tc-icon-toggle-border: tokens.$coral-border-s-solid tokens.$coral-color-neutral-border !default; $tc-icon-toggle-tick-size: 12px !default; @@ -87,8 +87,8 @@ $tc-icon-toggle-tick-size: 12px !default; width: $tc-icon-toggle-tick-size; height: $tc-icon-toggle-tick-size; border-radius: calc(#{$tc-icon-toggle-tick-size} / 2); - right: -0.4rem; - top: -0.4rem; + right: -0.25rem; + top: -0.25rem; background: tokens.$coral-color-accent-text; border: tokens.$coral-border-s-solid tokens.$coral-color-neutral-border-weak; } diff --git a/packages/components/src/Actions/ActionIconToggle/IconToggle.stories.js b/packages/components/src/Actions/ActionIconToggle/IconToggle.stories.js index 859efd7c09..6b6a612953 100644 --- a/packages/components/src/Actions/ActionIconToggle/IconToggle.stories.js +++ b/packages/components/src/Actions/ActionIconToggle/IconToggle.stories.js @@ -1,4 +1,5 @@ import { Component, Fragment } from 'react'; + import { action } from '@storybook/addon-actions'; import ActionIconToggle from './ActionIconToggle.component'; @@ -94,8 +95,8 @@ export const CustomizeSizes = () => (
     			{`// sass file
     @import '~@talend/react-components/lib/Actions/ActionIconToggle/ActionIconToggle.scss'
    -$my-btn-size: 4rem;
    -$my-btn-icon-size: 2.5rem;
    +$my-btn-size: 2.5rem;
    +$my-btn-icon-size: 1.5625rem;
     .tc-icon-toggle.my-custom-icon-toggle {
         @include tc-icon-toggle($my-btn-size, $my-btn-icon-size);
     }`}
    @@ -110,14 +111,14 @@ $my-btn-icon-size: 2.5rem;
     
     		
     
    diff --git a/packages/components/src/Actions/ActionSplitDropdown/ActionSplitDropdown.module.scss b/packages/components/src/Actions/ActionSplitDropdown/ActionSplitDropdown.module.scss
    index 922351c1a4..abfda93cd9 100644
    --- a/packages/components/src/Actions/ActionSplitDropdown/ActionSplitDropdown.module.scss
    +++ b/packages/components/src/Actions/ActionSplitDropdown/ActionSplitDropdown.module.scss
    @@ -1,4 +1,4 @@
    -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *;
    +@use '@talend/bootstrap-theme/src/theme/guidelines' as *;
     
     .tc-split-dropdown {
     	li>a {
    diff --git a/packages/components/src/Actions/ActionSplitDropdown/ActionSplitDropdown.test.js b/packages/components/src/Actions/ActionSplitDropdown/ActionSplitDropdown.test.js
    index b48b09b4b1..22bbb17675 100644
    --- a/packages/components/src/Actions/ActionSplitDropdown/ActionSplitDropdown.test.js
    +++ b/packages/components/src/Actions/ActionSplitDropdown/ActionSplitDropdown.test.js
    @@ -1,6 +1,8 @@
     import { render, screen } from '@testing-library/react';
     import userEvent from '@testing-library/user-event';
    +
     import ActionSplitDropdown from './ActionSplitDropdown.component';
    +
     jest.unmock('@talend/design-system');
     
     const items = [
    @@ -100,7 +102,9 @@ describe('ActionSplitDropdown', () => {
     		expect(screen.getByText('No option')).toBeInTheDocument();
     	});
     
    -	it('should render trigger event', () => {
    +	it('should render trigger event', async () => {
    +		const user = userEvent.setup();
    +
     		// given
     		const onItemClick = jest.fn();
     		const props = {
    @@ -124,7 +128,7 @@ describe('ActionSplitDropdown', () => {
     
     		// when
     		render();
    -		userEvent.click(screen.getByRole('menuitem', { name: 'Item 1' }));
    +		await user.click(screen.getByRole('menuitem', { name: 'Item 1' }));
     
     		// then
     		expect(onItemClick.mock.calls[0][1]).toEqual({
    @@ -134,7 +138,7 @@ describe('ActionSplitDropdown', () => {
     		expect(onItemClick.mock.calls[0][0].type).toEqual('click');
     
     		// when
    -		userEvent.click(screen.getByRole('menuitem', { name: 'Item 2' }));
    +		await user.click(screen.getByRole('menuitem', { name: 'Item 2' }));
     
     		// then
     		expect(onItemClick.mock.calls[1][1]).toEqual({
    diff --git a/packages/components/src/AppGuidedTour/AppGuidedTour.component.js b/packages/components/src/AppGuidedTour/AppGuidedTour.component.js
    index b6bcbc4fce..7c56e1601c 100644
    --- a/packages/components/src/AppGuidedTour/AppGuidedTour.component.js
    +++ b/packages/components/src/AppGuidedTour/AppGuidedTour.component.js
    @@ -1,11 +1,13 @@
    -import { useState, useEffect } from 'react';
    -import PropTypes from 'prop-types';
    -import useLocalStorage from 'react-use/lib/useLocalStorage';
    +import { useEffect, useState } from 'react';
     import { useTranslation } from 'react-i18next';
    +import useLocalStorage from 'react-use/lib/useLocalStorage';
    +
    +import PropTypes from 'prop-types';
    +
    +import I18N_DOMAIN_COMPONENTS from '../constants';
     import GuidedTour from '../GuidedTour';
    -import Toggle from '../Toggle';
     import Stepper from '../Stepper';
    -import I18N_DOMAIN_COMPONENTS from '../constants';
    +import Toggle from '../Toggle';
     import DemoContentStep from './DemoContentStep.component';
     
     const DEMO_CONTENT_STEP_ID = 1;
    @@ -21,12 +23,19 @@ function AppGuidedTour({
     	onImportDemoContent,
     	onRequestClose,
     	welcomeStepBody = null,
    +	tourId,
     	...rest
     }) {
     	const { t } = useTranslation(I18N_DOMAIN_COMPONENTS);
     	const [isAlreadyViewed, setIsAlreadyViewed] = useLocalStorage(localStorageKey, false);
     	const [importDemoContent, setImportDemoContent] = useState(demoContentSteps && !isAlreadyViewed);
     	const [currentStep, setCurrentStep] = useState(0);
    +	// Reset currentStep to 0 when tour is opened
    +	useEffect(() => {
    +		if (isOpen) {
    +			setCurrentStep(0);
    +		}
    +	}, [isOpen]);
     
     	const isNavigationDisabled =
     		importDemoContent &&
    @@ -52,6 +61,7 @@ function AppGuidedTour({
     
     	return (
     		 {
     				onRequestClose();
     				setIsAlreadyViewed(true);
    -				setCurrentStep(0);
    -				setImportDemoContent(false);
    +				if (importDemoContent) {
    +					setImportDemoContent(false);
    +					setCurrentStep(Math.max(0, currentStep - 1));
    +				}
     			}}
     			steps={[
     				{
    @@ -100,6 +112,7 @@ function AppGuidedTour({
     														setImportDemoContent(event.target.checked);
     													}}
     													checked={importDemoContent}
    +													data-feature={tourId && `guidedtour.${tourId}.demo`}
     												/>
     											
     										)}
    @@ -134,6 +147,7 @@ AppGuidedTour.propTypes = {
     	onRequestOpen: PropTypes.func.isRequired,
     	onImportDemoContent: PropTypes.func,
     	onRequestClose: PropTypes.func.isRequired,
    +	tourId: PropTypes.string,
     };
     
     export default AppGuidedTour;
    diff --git a/packages/components/src/AppGuidedTour/AppGuidedTour.stories.js b/packages/components/src/AppGuidedTour/AppGuidedTour.stories.js
    index 018c2914fd..7ab6201524 100644
    --- a/packages/components/src/AppGuidedTour/AppGuidedTour.stories.js
    +++ b/packages/components/src/AppGuidedTour/AppGuidedTour.stories.js
    @@ -22,6 +22,7 @@ function AppGuidedTourContainer({ withDemoContent = false }) {
     
     	return (
     		 {
     		localStorage.setItem(DEFAULT_LOCAL_STORAGE_KEY, null);
     	});
     	it('should not trigger import function if "load demo content" is not selected', async () => {
    +		const user = userEvent.setup();
    +
     		const onImportDemoContentMock = jest.fn();
     		render();
     
    -		await userEvent.click(screen.getByLabelText('Import demo content'));
    -		await userEvent.click(screen.getByText('Let me try'));
    +		await user.click(screen.getByLabelText('Import demo content'));
    +		await user.click(screen.getByText('Let me try'));
     		expect(onImportDemoContentMock).not.toHaveBeenCalled();
     	});
     	it('should trigger import function if "load demo content" is selected', async () => {
    +		const user = userEvent.setup();
    +
     		const onImportDemoContentMock = jest.fn();
     
     		render();
     		const nextBtn = document.querySelector('button[data-tour-elem="right-arrow"]');
     		expect(nextBtn).toBeInTheDocument();
    -		await userEvent.click(nextBtn);
    +		await user.click(nextBtn);
     		expect(onImportDemoContentMock).toHaveBeenCalled();
     	});
     	it('should import content by default on first time use', () => {
    @@ -45,13 +50,15 @@ describe('AppGuidedTour', () => {
     		render();
     		expect(screen.getByLabelText('Import demo content')).not.toBeChecked();
     	});
    -	it('should reset state on close', () => {
    +	it('should reset state on close', async () => {
    +		const user = userEvent.setup();
    +
     		const onRequestCloseMock = jest.fn();
     		localStorage.setItem(DEFAULT_LOCAL_STORAGE_KEY, 'true');
     		render();
     		const nextBtn = document.querySelector('button[data-tour-elem="right-arrow"]');
    -		userEvent.click(nextBtn);
    -		userEvent.click(screen.getByText('Let me try'));
    +		await user.click(nextBtn);
    +		await user.click(screen.getByText('Let me try'));
     
     		expect(onRequestCloseMock).toHaveBeenCalled();
     	});
    @@ -66,12 +73,14 @@ describe('AppGuidedTour', () => {
     		render();
     		expect(onRequestOpenMock).not.toHaveBeenCalled();
     	});
    -	it('Should set a local storage flag when closed', () => {
    +	it('Should set a local storage flag when closed', async () => {
    +		const user = userEvent.setup();
    +
     		const onCloseMock = jest.fn();
     		render();
     		const nextBtn = document.querySelector('button[data-tour-elem="right-arrow"]');
    -		userEvent.click(nextBtn);
    -		userEvent.click(screen.getByText('Let me try'));
    +		await user.click(nextBtn);
    +		await user.click(screen.getByText('Let me try'));
     		expect(localStorage.getItem(DEFAULT_LOCAL_STORAGE_KEY)).toBe('true');
     	});
     	it('Should not show demo content form if no step is provided', async () => {
    @@ -86,4 +95,21 @@ describe('AppGuidedTour', () => {
     		);
     		expect(screen.queryByText('Import demo content')).not.toBeInTheDocument();
     	});
    +	it('Should stay on the last page when finished', async () => {
    +		const user = userEvent.setup();
    +		const steps = [
    +			{
    +				content: {
    +					header: 'Header',
    +					body: () => 'Last page',
    +				},
    +			},
    +		];
    +		render();
    +		expect(screen.queryByText(/Last page/i)).not.toBeInTheDocument();
    +		const nextBtn = document.querySelector('button[data-tour-elem="right-arrow"]');
    +		await user.click(nextBtn);
    +		await user.click(screen.getByText('Let me try'));
    +		expect(screen.queryByText(/Last page/i)).toBeInTheDocument();
    +	});
     });
    diff --git a/packages/components/src/AppGuidedTour/DemoContentStep.module.scss b/packages/components/src/AppGuidedTour/DemoContentStep.module.scss
    index f559858a51..745b1c2e33 100644
    --- a/packages/components/src/AppGuidedTour/DemoContentStep.module.scss
    +++ b/packages/components/src/AppGuidedTour/DemoContentStep.module.scss
    @@ -1,4 +1,4 @@
    -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *;
    +@use '@talend/bootstrap-theme/src/theme/guidelines' as *;
     
     .info {
     	white-space: pre;
    diff --git a/packages/components/src/AppSwitcher/AppSwitcher.component.js b/packages/components/src/AppSwitcher/AppSwitcher.component.js
    index d6ffadd50e..82a129b544 100644
    --- a/packages/components/src/AppSwitcher/AppSwitcher.component.js
    +++ b/packages/components/src/AppSwitcher/AppSwitcher.component.js
    @@ -1,13 +1,14 @@
     import { useTranslation } from 'react-i18next';
    +
     import PropTypes from 'prop-types';
    -import I18N_DOMAIN_COMPONENTS from '../constants';
     
     import Action from '../Actions/Action';
     import ActionDropdown from '../Actions/ActionDropdown';
    +import I18N_DOMAIN_COMPONENTS from '../constants';
     import Inject from '../Inject';
    +import { getTheme } from '../theme';
     
     import AppSwitcherCSSModule from './AppSwitcher.module.scss';
    -import { getTheme } from '../theme';
     
     const theme = getTheme(AppSwitcherCSSModule);
     
    diff --git a/packages/components/src/AppSwitcher/AppSwitcher.component.test.js b/packages/components/src/AppSwitcher/AppSwitcher.component.test.js
    index 64ff778413..1da70bc2dc 100644
    --- a/packages/components/src/AppSwitcher/AppSwitcher.component.test.js
    +++ b/packages/components/src/AppSwitcher/AppSwitcher.component.test.js
    @@ -1,11 +1,14 @@
    -import userEvent from '@testing-library/user-event';
     import { render, screen } from '@testing-library/react';
    +import userEvent from '@testing-library/user-event';
    +
     import AppSwitcher from './AppSwitcher.component';
     
     jest.unmock('@talend/design-system');
     
     describe('AppSwitcher', () => {
    -	it('should render the products', () => {
    +	it('should render the products', async () => {
    +		const user = userEvent.setup();
    +
     		const brand = {
     			id: 'brand',
     			label: 'My App',
    @@ -32,13 +35,14 @@ describe('AppSwitcher', () => {
     		render();
     		expect(screen.getByText('My App')).toBeInTheDocument();
     		expect(screen.getByText('Data Preparation')).toBeInTheDocument();
    -		userEvent.click(screen.getByText('My App'));
    -		userEvent.click(screen.getByText('Data Preparation'));
    +		await user.click(screen.getByText('My App'));
    +		await user.click(screen.getByText('Data Preparation'));
     		expect(brand.items[0].onClick).toHaveBeenCalled();
     		expect(brand.onClick).not.toHaveBeenCalled();
     	});
     
    -	it('should render with a Action', () => {
    +	it('should render with a Action', async () => {
    +		const user = userEvent.setup();
     		const brand = {
     			id: 'brand',
     			label: 'My App',
    @@ -46,7 +50,7 @@ describe('AppSwitcher', () => {
     		};
     		render();
     		expect(screen.getByText('My App')).toBeInTheDocument();
    -		userEvent.click(screen.getByText('My App'));
    +		await user.click(screen.getByText('My App'));
     		expect(brand.onClick).toHaveBeenCalled();
     	});
     
    diff --git a/packages/components/src/AppSwitcher/AppSwitcher.module.scss b/packages/components/src/AppSwitcher/AppSwitcher.module.scss
    index a2cfe3a90b..8701418c6f 100644
    --- a/packages/components/src/AppSwitcher/AppSwitcher.module.scss
    +++ b/packages/components/src/AppSwitcher/AppSwitcher.module.scss
    @@ -1,10 +1,14 @@
    -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *;
    -@use '~@talend/design-tokens/lib/tokens';
    +@use '@talend/bootstrap-theme/src/theme/guidelines' as *;
    +@use '@talend/design-tokens/lib/tokens' as tokens;
     
     .tc-app-switcher {
     	:global(.tc-svg-icon:first-child) {
     		height: $svg-lg-size;
     		width: $svg-lg-size;
    +
    +		path {
    +			fill: tokens.$coral-color-brand-text;
    +		}
     	}
     }
     
    diff --git a/packages/components/src/Badge/Badge.module.scss b/packages/components/src/Badge/Badge.module.scss
    index 931c3a1ba9..883dffefca 100644
    --- a/packages/components/src/Badge/Badge.module.scss
    +++ b/packages/components/src/Badge/Badge.module.scss
    @@ -1,25 +1,25 @@
    -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *;
    -@use '@talend/design-tokens/lib/tokens';
    +@use '@talend/bootstrap-theme/src/theme/guidelines' as *;
    +@use '@talend/design-tokens/lib/tokens' as tokens;
     
    -$tc-badge-large-label-font-size: 1.4rem !default;
    +$tc-badge-large-label-font-size: 0.875rem !default;
     $tc-badge-large-label-font-weight: normal !default;
     $tc-badge-large-label-with-categ-font-weight: $font-weight-semi-bold !default;
    -$tc-badge-large-height: 2.4rem !default;
    +$tc-badge-large-height: 1.5rem !default;
     $tc-badge-large-vertical-padding: calc((#{$tc-badge-large-height} - #{$tc-badge-large-label-font-size} * #{$line-height-base}) / 2) !default; // $line-height-base / 2
     $tc-badge-large-margin: $padding-smaller !default;
     $tc-badge-large-delete-icon-size: $svg-xs-size !default;
     $tc-badge-large-icon-size: $svg-xs-size !default;
     $tc-badge-large-padding: $padding-small;
     
    -$tc-badge-small-label-font-size: 1.2rem !default;
    +$tc-badge-small-label-font-size: 0.75rem !default;
     $tc-badge-small-label-font-weight: normal !default;
     $tc-badge-small-label-with-categ-font-weight: $font-weight-semi-bold !default;
    -$tc-badge-small-height: 2rem !default;
    +$tc-badge-small-height: 1.25rem !default;
     $tc-badge-small-vertical-padding: calc((#{$tc-badge-small-height} - #{$tc-badge-small-label-font-size} * #{$line-height-base}) / 2) !default; // $line-height-base / 2
     $tc-badge-small-margin: $padding-smaller !default;
     $tc-badge-small-delete-icon-size: calc(#{$svg-sm-size} / 2) !default;
     $tc-badge-small-icon-size: $svg-xs-size !default;
    -$tc-badge-small-padding: 0.8rem;
    +$tc-badge-small-padding: 0.5rem;
     
     $tc-badge-disabled-opacity: 0.62;
     
    @@ -43,7 +43,7 @@ $tc-badge-disabled-opacity: 0.62;
     
     .tc-badge {
     	display: inline-flex;
    -	max-width: 23.5rem;
    +	max-width: 14.6875rem;
     
     	&:not(.tc-badge-readonly) {
     		cursor: pointer;
    @@ -137,7 +137,7 @@ $tc-badge-disabled-opacity: 0.62;
     			color: tokens.$coral-color-neutral-text;
     
     			span {
    -				max-width: 16rem;
    +				max-width: 10rem;
     				display: block;
     				text-overflow: ellipsis;
     				overflow: hidden;
    @@ -162,7 +162,7 @@ $tc-badge-disabled-opacity: 0.62;
     
     	&.tc-badge-display-large {
     		.tc-badge-separator {
    -			margin-top: 0.4rem;
    +			margin-top: 0.25rem;
     		}
     
     		.tc-badge-button {
    @@ -222,7 +222,7 @@ $tc-badge-disabled-opacity: 0.62;
     
     	&.tc-badge-display-small {
     		.tc-badge-separator {
    -			margin-top: 0.3rem;
    +			margin-top: 0.1875rem;
     		}
     
     		.tc-badge-button {
    diff --git a/packages/components/src/Badge/BadgeComposition/BadgeDelete/BadgeDelete.component.test.js b/packages/components/src/Badge/BadgeComposition/BadgeDelete/BadgeDelete.component.test.js
    index 207262c3d5..40b6b93fe6 100644
    --- a/packages/components/src/Badge/BadgeComposition/BadgeDelete/BadgeDelete.component.test.js
    +++ b/packages/components/src/Badge/BadgeComposition/BadgeDelete/BadgeDelete.component.test.js
    @@ -1,7 +1,8 @@
     import { render, screen } from '@testing-library/react';
     import userEvent from '@testing-library/user-event';
    -import BadgeDelete from './BadgeDelete.component';
    +
     import getDefaultT from '../../../translate';
    +import BadgeDelete from './BadgeDelete.component';
     
     describe('BadgeDelete', () => {
     	it('should render', () => {
    @@ -17,7 +18,8 @@ describe('BadgeDelete', () => {
     		// then
     		expect(screen.getByLabelText('Delete')).toBeInTheDocument();
     	});
    -	it('should trigger on click function', () => {
    +	it('should trigger on click function', async () => {
    +		const user = userEvent.setup();
     		// given
     		const onClick = jest.fn();
     		const props = {
    @@ -28,7 +30,7 @@ describe('BadgeDelete', () => {
     		// when
     		render();
     		// then
    -		userEvent.click(screen.getByLabelText('Delete'));
    +		await user.click(screen.getByLabelText('Delete'));
     		expect(onClick).toHaveBeenCalledTimes(1);
     	});
     	it('should pass the props label to the button', () => {
    diff --git a/packages/components/src/Badge/BadgeComposition/BadgeDropdown/BadgeDropdown.component.test.js b/packages/components/src/Badge/BadgeComposition/BadgeDropdown/BadgeDropdown.component.test.js
    index 76efbed61c..e5d17e7404 100644
    --- a/packages/components/src/Badge/BadgeComposition/BadgeDropdown/BadgeDropdown.component.test.js
    +++ b/packages/components/src/Badge/BadgeComposition/BadgeDropdown/BadgeDropdown.component.test.js
    @@ -1,9 +1,12 @@
    -import { screen, render } from '@testing-library/react';
    +import { render, screen } from '@testing-library/react';
     import userEvent from '@testing-library/user-event';
    +
     import BadgeDropdown from './BadgeDropdown.component';
     
     describe('BadgeDropdown', () => {
    -	it('should render a dropdown', () => {
    +	it('should render a dropdown', async () => {
    +		const user = userEvent.setup();
    +
     		// given
     		const dropdownProps = {
     			id: 'context-dropdown-related-items',
    @@ -31,7 +34,7 @@ describe('BadgeDropdown', () => {
     		// then
     		expect(screen.getByText('Label')).toBeInTheDocument();
     		expect(screen.getByText('document 1')).toBeInTheDocument();
    -		userEvent.click(screen.getByText('document 1'));
    +		await user.click(screen.getByText('document 1'));
     		expect(dropdownProps.items[0].onClick).toHaveBeenCalledTimes(1);
     	});
     });
    diff --git a/packages/components/src/Breadcrumbs/Breadcrumbs.module.scss b/packages/components/src/Breadcrumbs/Breadcrumbs.module.scss
    index ce89774837..32b5c63a1e 100644
    --- a/packages/components/src/Breadcrumbs/Breadcrumbs.module.scss
    +++ b/packages/components/src/Breadcrumbs/Breadcrumbs.module.scss
    @@ -1,4 +1,4 @@
    -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *;
    +@use '@talend/bootstrap-theme/src/theme/guidelines' as *;
     
     ////
     /// Breadcrumb
    diff --git a/packages/components/src/Breadcrumbs/Breadcrumbs.test.js b/packages/components/src/Breadcrumbs/Breadcrumbs.test.js
    index 1300a33e67..ed1bc887c8 100644
    --- a/packages/components/src/Breadcrumbs/Breadcrumbs.test.js
    +++ b/packages/components/src/Breadcrumbs/Breadcrumbs.test.js
    @@ -1,5 +1,6 @@
    -import { screen, render } from '@testing-library/react';
    +import { render, screen } from '@testing-library/react';
     import userEvent from '@testing-library/user-event';
    +
     import Breadcrumbs from './Breadcrumbs.component';
     
     jest.unmock('@talend/design-system');
    @@ -70,19 +71,21 @@ describe('Breadcrumbs', () => {
     			{ text: 'Text C', onClick: onTextCClick },
     		];
     
    -		it('should trigger action callback on item click', () => {
    +		it('should trigger action callback on item click', async () => {
    +			const user = userEvent.setup();
    +
     			// given
     			const clickedElementIndex = 1;
     
     			// when
     			const breadcrumbs = ;
     			render(breadcrumbs);
    -			userEvent.click(screen.getByText(actions[clickedElementIndex].text));
    +			await user.click(screen.getByText(actions[clickedElementIndex].text));
     
     			// then
    -			expect(onTextAClick).not.toBeCalled();
    -			expect(onTextBClick).toBeCalled();
    -			expect(onTextCClick).not.toBeCalled();
    +			expect(onTextAClick).not.toHaveBeenCalled();
    +			expect(onTextBClick).toHaveBeenCalled();
    +			expect(onTextCClick).not.toHaveBeenCalled();
     
     			const callArgs = onTextBClick.mock.calls[0];
     			expect(callArgs[1]).toBe(actions[clickedElementIndex]);
    diff --git a/packages/components/src/CircularProgress/CircularProgress.module.scss b/packages/components/src/CircularProgress/CircularProgress.module.scss
    index c7aeb38422..b681843c9f 100644
    --- a/packages/components/src/CircularProgress/CircularProgress.module.scss
    +++ b/packages/components/src/CircularProgress/CircularProgress.module.scss
    @@ -1,11 +1,11 @@
    -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *;
    -@use '~@talend/design-tokens/lib/tokens';
    +@use '@talend/bootstrap-theme/src/theme/guidelines' as *;
    +@use '@talend/design-tokens/lib/tokens' as tokens;
     
     // CircularProgress styles
     $tc-circular-progress-light-fill: tokens.$coral-color-neutral-text-inverted !default;
    -$tc-circular-progress-small: 1.2rem;
    -$tc-circular-progress-regular: 2rem;
    -$tc-circular-progress-large: 4rem;
    +$tc-circular-progress-small: 0.75rem;
    +$tc-circular-progress-regular: 1.25rem;
    +$tc-circular-progress-large: 2.5rem;
     $tc-circular-progress-stroke-width: 5;
     
     .fixed {
    diff --git a/packages/components/src/CollapsiblePanel/CollapsiblePanel.module.scss b/packages/components/src/CollapsiblePanel/CollapsiblePanel.module.scss
    index e6209d3b2d..f4e08c2c4f 100644
    --- a/packages/components/src/CollapsiblePanel/CollapsiblePanel.module.scss
    +++ b/packages/components/src/CollapsiblePanel/CollapsiblePanel.module.scss
    @@ -1,5 +1,5 @@
    -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *;
    -@use '~@talend/design-tokens/lib/tokens';
    +@use '@talend/bootstrap-theme/src/theme/guidelines' as *;
    +@use '@talend/design-tokens/lib/tokens' as tokens;
     
     $tc-collapsible-panel-padding-smaller: $padding-smaller !default;
     $tc-collapsible-panel-padding-normal: $padding-normal !default;
    @@ -143,7 +143,7 @@ $tc-collapsible-panel-padding-larger: $padding-larger !default;
     
     	.panel-header-content {
     		display: flex;
    -		height: 4rem;
    +		height: 2.5rem;
     		align-items: center;
     		padding: 0 $padding-normal;
     
    @@ -158,7 +158,7 @@ $tc-collapsible-panel-padding-larger: $padding-larger !default;
     			justify-content: space-between;
     			flex-basis: 100%;
     			overflow: hidden;
    -			font-size: 1.4rem;
    +			font-size: 0.875rem;
     			width: 100%;
     			padding-right: $padding-smaller;
     
    @@ -185,13 +185,13 @@ $tc-collapsible-panel-padding-larger: $padding-larger !default;
     	}
     
     	:global(.tc-icon-toggle) {
    -		height: 1.6rem;
    -		width: 1.6rem;
    +		height: 1rem;
    +		width: 1rem;
     		transform-origin: center;
     
     		svg {
    -			height: 1rem;
    -			width: 1rem;
    +			height: 0.625rem;
    +			width: 0.625rem;
     		}
     	}
     
    @@ -308,7 +308,7 @@ $tc-collapsible-panel-padding-larger: $padding-larger !default;
     
     	.tag {
     		white-space: nowrap;
    -		font-size: 1.2rem;
    +		font-size: 0.75rem;
     	}
     
     	.detail {
    diff --git a/packages/components/src/CollapsiblePanel/CollapsiblePanel.test.js b/packages/components/src/CollapsiblePanel/CollapsiblePanel.test.js
    index d0c1fd7b71..0a72086dec 100644
    --- a/packages/components/src/CollapsiblePanel/CollapsiblePanel.test.js
    +++ b/packages/components/src/CollapsiblePanel/CollapsiblePanel.test.js
    @@ -1,5 +1,6 @@
     import { render, screen } from '@testing-library/react';
     import userEvent from '@testing-library/user-event';
    +
     import CollapsiblePanel from './CollapsiblePanel.component';
     
     const version1 = {
    @@ -34,7 +35,9 @@ const propsPanelWithActions = {
     };
     
     describe('CollapsiblePanel', () => {
    -	it('should trigger onSelect callback on header click', () => {
    +	it('should trigger onSelect callback on header click', async () => {
    +		const user = userEvent.setup();
    +
     		// given
     		const propsDescriptivePanel = {
     			header: [[version1, readOnlyLabel], timeStamp],
    @@ -61,22 +64,24 @@ describe('CollapsiblePanel', () => {
     
     		// when
     		render(panelInstance);
    -		userEvent.click(screen.getByText('Version 1 94a06b6a3a85bc415add5fdb31dcceebf96b8182'));
    +		await user.click(screen.getByText('Version 1 94a06b6a3a85bc415add5fdb31dcceebf96b8182'));
     
     		// then
    -		expect(propsDescriptivePanel.onSelect).toBeCalled();
    +		expect(propsDescriptivePanel.onSelect).toHaveBeenCalled();
     	});
     
    -	it('should trigger onToggle callback on header click', () => {
    +	it('should trigger onToggle callback on header click', async () => {
    +		const user = userEvent.setup();
    +
     		// given
     		const panelInstance = ;
     
     		// when
     		render(panelInstance);
    -		userEvent.click(screen.getByText('Successful'));
    +		await user.click(screen.getByText('Successful'));
     
     		// then
    -		expect(propsPanelWithActions.onToggle).toBeCalled();
    +		expect(propsPanelWithActions.onToggle).toHaveBeenCalled();
     	});
     
     	it('should render custom content in panel body', () => {
    diff --git a/packages/components/src/ConfirmDialog/ConfirmDialog.test.js b/packages/components/src/ConfirmDialog/ConfirmDialog.test.js
    index 734764e460..405b63335f 100644
    --- a/packages/components/src/ConfirmDialog/ConfirmDialog.test.js
    +++ b/packages/components/src/ConfirmDialog/ConfirmDialog.test.js
    @@ -1,5 +1,6 @@
    -import { screen, render } from '@testing-library/react';
    +import { render, screen } from '@testing-library/react';
     import userEvent from '@testing-library/user-event';
    +
     import ConfirmDialog from './ConfirmDialog.component';
     
     const children = 
    BODY
    ; @@ -104,7 +105,9 @@ describe('ConfirmDialog', () => { expect(screen.getByLabelText('This is loading')).toHaveAttribute('aria-valuenow', '25'); }); - it('should render with additional actions', () => { + it('should render with additional actions', async () => { + const user = userEvent.setup(); + // given const properties = { header: 'Hello world', @@ -126,7 +129,7 @@ describe('ConfirmDialog', () => { // then expect(screen.getByText('Hello world')).toBeVisible(); expect(screen.getByText('Keep on Github')).toBeVisible(); - userEvent.click(screen.getByText('Keep on Github')); + await user.click(screen.getByText('Keep on Github')); expect(properties.secondaryActions[0].onClick).toHaveBeenCalled(); }); }); diff --git a/packages/components/src/DataViewer/Badges/LengthBadge/LengthBadge.module.scss b/packages/components/src/DataViewer/Badges/LengthBadge/LengthBadge.module.scss index db8c830f33..0e483c08d1 100644 --- a/packages/components/src/DataViewer/Badges/LengthBadge/LengthBadge.module.scss +++ b/packages/components/src/DataViewer/Badges/LengthBadge/LengthBadge.module.scss @@ -1,4 +1,4 @@ -@use '~@talend/design-tokens/lib/tokens'; +@use '@talend/design-tokens/lib/tokens' as tokens; .tc-length-badge { background-color: tokens.$coral-color-neutral-background-medium; diff --git a/packages/components/src/DataViewer/Core/Tree/Tree.module.scss b/packages/components/src/DataViewer/Core/Tree/Tree.module.scss index 9fe43b9e9b..3827f782d1 100644 --- a/packages/components/src/DataViewer/Core/Tree/Tree.module.scss +++ b/packages/components/src/DataViewer/Core/Tree/Tree.module.scss @@ -1,5 +1,5 @@ -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *; -@use '~@talend/design-tokens/lib/tokens'; +@use '@talend/bootstrap-theme/src/theme/guidelines' as *; +@use '@talend/design-tokens/lib/tokens' as tokens; :global(.tc-model .tc-tree) { overflow: auto; diff --git a/packages/components/src/DataViewer/Headers/TreeHeader/TreeHeader.module.scss b/packages/components/src/DataViewer/Headers/TreeHeader/TreeHeader.module.scss index 7652522eb0..ebf33cb81a 100644 --- a/packages/components/src/DataViewer/Headers/TreeHeader/TreeHeader.module.scss +++ b/packages/components/src/DataViewer/Headers/TreeHeader/TreeHeader.module.scss @@ -1,11 +1,11 @@ -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *; -@use '~@talend/design-tokens/lib/tokens'; +@use '@talend/bootstrap-theme/src/theme/guidelines' as *; +@use '@talend/design-tokens/lib/tokens' as tokens; -$default-height: 4rem !default; +$default-height: 2.5rem !default; .tc-tree-header { align-items: center; - border-bottom: 0.1rem solid tokens.$coral-color-neutral-border; + border-bottom: 0.0625rem solid tokens.$coral-color-neutral-border; color: tokens.$coral-color-neutral-text; display: flex; font-weight: 600; diff --git a/packages/components/src/DataViewer/Headers/TreeHeader/TreeHeader.test.js b/packages/components/src/DataViewer/Headers/TreeHeader/TreeHeader.test.js index 1e1c3e7f21..6ff01a3317 100644 --- a/packages/components/src/DataViewer/Headers/TreeHeader/TreeHeader.test.js +++ b/packages/components/src/DataViewer/Headers/TreeHeader/TreeHeader.test.js @@ -1,5 +1,6 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; + import Component from './TreeHeader.component'; describe('TreeHeader', () => { @@ -9,17 +10,21 @@ describe('TreeHeader', () => { expect(container.firstChild).toMatchSnapshot(); }); it('should render with the collapse button', async () => { + const user = userEvent.setup(); + const onClickCollapseAll = jest.fn(); render(); expect(screen.getByTestId('collapse-all')).toBeVisible(); - await userEvent.click(screen.getByTestId('collapse-all')); + await user.click(screen.getByTestId('collapse-all')); expect(onClickCollapseAll).toHaveBeenCalled(); }); it('should render with the expand button', async () => { + const user = userEvent.setup(); + const onClickExpandAll = jest.fn(); render(); expect(screen.getByTestId('expand-all')).toBeVisible(); - await userEvent.click(screen.getByTestId('expand-all')); + await user.click(screen.getByTestId('expand-all')); expect(onClickExpandAll).toHaveBeenCalled(); }); it('should render other actions', () => { diff --git a/packages/components/src/DataViewer/Icons/TreeBranchIcon/TreeBranchIcon.module.scss b/packages/components/src/DataViewer/Icons/TreeBranchIcon/TreeBranchIcon.module.scss index 6ad38e2de6..65ff9d14fe 100644 --- a/packages/components/src/DataViewer/Icons/TreeBranchIcon/TreeBranchIcon.module.scss +++ b/packages/components/src/DataViewer/Icons/TreeBranchIcon/TreeBranchIcon.module.scss @@ -1,5 +1,5 @@ -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *; -@use '~@talend/design-tokens/lib/tokens'; +@use '@talend/bootstrap-theme/src/theme/guidelines' as *; +@use '@talend/design-tokens/lib/tokens' as tokens; .tc-tree-branch-icon { display: flex; diff --git a/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.test.js b/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.test.js index 46a3c141dd..a9d4ce332c 100644 --- a/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.test.js +++ b/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.test.js @@ -1,6 +1,7 @@ -import Immutable from 'immutable'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import Immutable from 'immutable'; + import TreeManager, { addPathsToCollection, removePathsFromCollection, @@ -29,6 +30,8 @@ describe('TreeManager#onToggle', () => { setState: jest.fn(), }; it('when the handler emitter is an union, and has been click for the first time', async () => { + const user = userEvent.setup(); + // given const options = { firstClickUnion: true, @@ -45,11 +48,13 @@ describe('TreeManager#onToggle', () => { )} />, ); - await userEvent.click(screen.getByTestId('btn')); + await user.click(screen.getByTestId('btn')); // then nothing expect(setStateSpy).not.toHaveBeenCalled(); }); it('default', async () => { + const user = userEvent.setup(); + // when const options = { firstClickUnion: false, @@ -66,7 +71,7 @@ describe('TreeManager#onToggle', () => { )} />, ); - await userEvent.click(screen.getByTestId('btn')); + await user.click(screen.getByTestId('btn')); // then expect(setStateSpy).toHaveBeenCalled(); }); diff --git a/packages/components/src/DataViewer/ModelViewer/ModelViewer.module.scss b/packages/components/src/DataViewer/ModelViewer/ModelViewer.module.scss index b2adbed63e..630ab68beb 100644 --- a/packages/components/src/DataViewer/ModelViewer/ModelViewer.module.scss +++ b/packages/components/src/DataViewer/ModelViewer/ModelViewer.module.scss @@ -1,8 +1,8 @@ -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *; -@use '~@talend/design-tokens/lib/tokens'; +@use '@talend/bootstrap-theme/src/theme/guidelines' as *; +@use '@talend/design-tokens/lib/tokens' as tokens; @import '~@talend/bootstrap-theme/src/theme/animation'; -$model-node-height: 3rem !default; +$model-node-height: 1.875rem !default; .tc-model { display: flex; @@ -86,7 +86,7 @@ $model-node-height: 3rem !default; &-quality-circles { display: inherit; - padding-top: 1.3rem; + padding-top: 0.8125rem; &-blink { @include heartbeat(object-blink); diff --git a/packages/components/src/DataViewer/RecordsViewer/Branch/RecordsViewerBranch.component.js b/packages/components/src/DataViewer/RecordsViewer/Branch/RecordsViewerBranch.component.js index a017c8f5f2..eaa174406b 100644 --- a/packages/components/src/DataViewer/RecordsViewer/Branch/RecordsViewerBranch.component.js +++ b/packages/components/src/DataViewer/RecordsViewer/Branch/RecordsViewerBranch.component.js @@ -1,11 +1,13 @@ import { Component } from 'react'; -import get from 'lodash/get'; + import classNames from 'classnames'; +import get from 'lodash/get'; import PropTypes from 'prop-types'; -import keycode from 'keycode'; + import Skeleton from '../../../Skeleton'; import { LengthBadge } from '../../Badges'; import { TreeBranchIcon } from '../../Icons'; + import theme from '../RecordsViewer.module.scss'; /** @@ -38,9 +40,10 @@ export class RecordsViewerBranch extends Component { }; onKeyDown = event => { - switch (event.keyCode) { - case keycode.codes.enter: - case keycode.codes.space: + switch (event.key) { + case 'Enter': + case ' ': + case 'Space': event.preventDefault(); // prevent scroll with space event.stopPropagation(); this.onClickRecordsBranch(event); diff --git a/packages/components/src/DataViewer/RecordsViewer/Branch/RecordsViewerBranch.test.js b/packages/components/src/DataViewer/RecordsViewer/Branch/RecordsViewerBranch.test.js index deb8119bd2..3e28217fef 100644 --- a/packages/components/src/DataViewer/RecordsViewer/Branch/RecordsViewerBranch.test.js +++ b/packages/components/src/DataViewer/RecordsViewer/Branch/RecordsViewerBranch.test.js @@ -1,6 +1,8 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; + import Component, { isLoaded } from './RecordsViewerBranch.component'; + jest.unmock('@talend/design-system'); describe('isLoading', () => { @@ -37,7 +39,9 @@ const schema = { describe('RecordsViewerBranch', () => { const dataKey = 'myDataKey'; - it('should render the branch with children', () => { + it('should render the branch with children', async () => { + const user = userEvent.setup(); + const onToggle = jest.fn(); const props = { @@ -58,9 +62,9 @@ describe('RecordsViewerBranch', () => { }; const { container } = render(); expect(container.firstChild).toMatchSnapshot(); - userEvent.click(screen.getByTestId('records-branch')); - userEvent.keyboard('{enter}'); - userEvent.keyboard('{space}'); + await user.click(screen.getByTestId('records-branch')); + await user.keyboard('{Enter}'); + await user.keyboard('{Space}'); expect(onToggle).toHaveBeenCalledWith( expect.anything(), diff --git a/packages/components/src/DataViewer/RecordsViewer/CellRenderer/RecordsCellRenderer.test.js b/packages/components/src/DataViewer/RecordsViewer/CellRenderer/RecordsCellRenderer.test.js index 116eee77f8..d091351e22 100644 --- a/packages/components/src/DataViewer/RecordsViewer/CellRenderer/RecordsCellRenderer.test.js +++ b/packages/components/src/DataViewer/RecordsViewer/CellRenderer/RecordsCellRenderer.test.js @@ -1,5 +1,6 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; + import Component from './RecordsCellRenderer.component'; jest.mock('../../Core', () => ({ @@ -14,13 +15,15 @@ jest.mock('../../Core', () => ({ jest.unmock('@talend/design-system'); describe('RecordsCellRenderer', () => { - it('should render Tree from Core', () => { + it('should render Tree from Core', async () => { + const user = userEvent.setup(); + const onToggle = jest.fn(); render( , ); expect(screen.getByTestId('tree')).toBeInTheDocument(); - userEvent.click(screen.getByRole('button')); + await user.click(screen.getByRole('button')); expect(onToggle).toHaveBeenCalled(); const props = JSON.parse(screen.getByTestId('props').dataset.props); expect(props).toMatchSnapshot(); diff --git a/packages/components/src/DataViewer/RecordsViewer/RecordsViewer.module.scss b/packages/components/src/DataViewer/RecordsViewer/RecordsViewer.module.scss index 258026c723..5f08021d44 100644 --- a/packages/components/src/DataViewer/RecordsViewer/RecordsViewer.module.scss +++ b/packages/components/src/DataViewer/RecordsViewer/RecordsViewer.module.scss @@ -1,10 +1,10 @@ /* stylelint-disable declaration-no-important */ -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *; -@use '~@talend/design-tokens/lib/tokens'; +@use '@talend/bootstrap-theme/src/theme/guidelines' as *; +@use '@talend/design-tokens/lib/tokens' as tokens; -$hightlight-height: 2.2rem !default; +$hightlight-height: 1.375rem !default; $records-node-height: $hightlight-height !default; -$border-size: 0.1rem; +$border-size: 0.0625rem; @mixin selection($backgroundColor) { background: $backgroundColor; @@ -32,8 +32,8 @@ $border-size: 0.1rem; } :global(.tc-svg-icon) { - height: 1.2rem; - width: 1.2rem; + height: 0.75rem; + width: 0.75rem; margin: 0; vertical-align: baseline; } @@ -149,7 +149,7 @@ $border-size: 0.1rem; .tc-leaf-overflow-icon { position: absolute; - left: -1.7rem; + left: -1.0625rem; margin-right: $padding-smaller; &-chevron { diff --git a/packages/components/src/DataViewer/Text/SimpleTextKeyValue/DefaultValueRenderer.module.scss b/packages/components/src/DataViewer/Text/SimpleTextKeyValue/DefaultValueRenderer.module.scss index 1ff3eb7f72..8f2ae7efde 100644 --- a/packages/components/src/DataViewer/Text/SimpleTextKeyValue/DefaultValueRenderer.module.scss +++ b/packages/components/src/DataViewer/Text/SimpleTextKeyValue/DefaultValueRenderer.module.scss @@ -1,4 +1,4 @@ -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *; +@use '@talend/bootstrap-theme/src/theme/guidelines' as *; .td-default-cell { height: 100%; diff --git a/packages/components/src/DataViewer/Text/SimpleTextKeyValue/SimpleTextKeyValue.module.scss b/packages/components/src/DataViewer/Text/SimpleTextKeyValue/SimpleTextKeyValue.module.scss index 93b7a128c4..5f735eb2dd 100644 --- a/packages/components/src/DataViewer/Text/SimpleTextKeyValue/SimpleTextKeyValue.module.scss +++ b/packages/components/src/DataViewer/Text/SimpleTextKeyValue/SimpleTextKeyValue.module.scss @@ -1,5 +1,5 @@ -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *; -@use '~@talend/design-tokens/lib/tokens'; +@use '@talend/bootstrap-theme/src/theme/guidelines' as *; +@use '@talend/design-tokens/lib/tokens' as tokens; .tc-simple-text { display: flex; diff --git a/packages/components/src/DataViewer/Virtualized/VirtualizedTree/VirtualizedTree.module.scss b/packages/components/src/DataViewer/Virtualized/VirtualizedTree/VirtualizedTree.module.scss index 67da66aa1e..c9052dd469 100644 --- a/packages/components/src/DataViewer/Virtualized/VirtualizedTree/VirtualizedTree.module.scss +++ b/packages/components/src/DataViewer/Virtualized/VirtualizedTree/VirtualizedTree.module.scss @@ -1,4 +1,4 @@ -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *; +@use '@talend/bootstrap-theme/src/theme/guidelines' as *; .tc-virtualized-tree { flex: 1 1 auto; diff --git a/packages/components/src/DataViewer/theme.module.scss b/packages/components/src/DataViewer/theme.module.scss index 2f76a7be1c..ade64350cd 100644 --- a/packages/components/src/DataViewer/theme.module.scss +++ b/packages/components/src/DataViewer/theme.module.scss @@ -1,8 +1,8 @@ -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *; -@use '~@talend/design-tokens/lib/tokens'; +@use '@talend/bootstrap-theme/src/theme/guidelines' as *; +@use '@talend/design-tokens/lib/tokens' as tokens; -$tc-layout-skeleton-width: 9.6rem !default; -$tc-layout-skeleton-height: 9.6rem !default; +$tc-layout-skeleton-width: 6rem !default; +$tc-layout-skeleton-height: 6rem !default; .tc-twoviewers-layout { display: flex; @@ -26,8 +26,8 @@ $tc-layout-skeleton-height: 9.6rem !default; } &-left { - border-right: 0.5rem solid tokens.$coral-color-neutral-border-weak; - width: 47rem; + border-right: 0.3125rem solid tokens.$coral-color-neutral-border-weak; + width: 29.375rem; } &-right { diff --git a/packages/components/src/Datalist/Datalist.component.js b/packages/components/src/Datalist/Datalist.component.js index 5a1872cf05..60424fb928 100644 --- a/packages/components/src/Datalist/Datalist.component.js +++ b/packages/components/src/Datalist/Datalist.component.js @@ -1,16 +1,18 @@ /* eslint-disable react/jsx-no-bind */ -import { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; + import classNames from 'classnames'; -import omit from 'lodash/omit'; -import keycode from 'keycode'; import get from 'lodash/get'; -import Typeahead from '../Typeahead'; -import theme from './Datalist.module.scss'; +import omit from 'lodash/omit'; +import PropTypes from 'prop-types'; + +import I18N_DOMAIN_COMPONENTS from '../constants'; import FocusManager from '../FocusManager'; import Icon from '../Icon'; -import { useTranslation } from 'react-i18next'; -import I18N_DOMAIN_COMPONENTS from '../constants'; +import Typeahead from '../Typeahead'; + +import theme from './Datalist.module.scss'; export function escapeRegexCharacters(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); @@ -24,7 +26,7 @@ const DISPLAY = { }; function isValuePresentInSuggestions(titleMap, filterValue, multiSection) { - return !!multiSection + return multiSection ? titleMap.find(group => group.suggestions.find(item => filterValue.toLowerCase() === item.name.toLowerCase()), ) @@ -364,13 +366,14 @@ function Datalist(props) { newHighlightedItemIndex, newHighlightedSectionIndex, } = params; - switch (event.which) { - case keycode.codes.esc: + switch (event.key) { + case 'Esc': + case 'Escape': event.preventDefault(); resetFilter(); hideSuggestions(); break; - case keycode.codes.enter: + case 'Enter': if (!suggestions) { break; } @@ -386,8 +389,10 @@ function Datalist(props) { persistValue(event); } break; - case keycode.codes.down: - case keycode.codes.up: + case 'Down': + case 'ArrowDown': + case 'Up': + case 'ArrowUp': event.preventDefault(); if (!suggestions) { // display all suggestions when they are not displayed diff --git a/packages/components/src/Datalist/Datalist.component.test.js b/packages/components/src/Datalist/Datalist.component.test.js index 2bbf03ccfc..e3aadca9f9 100644 --- a/packages/components/src/Datalist/Datalist.component.test.js +++ b/packages/components/src/Datalist/Datalist.component.test.js @@ -1,4 +1,4 @@ -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import Datalist from './Datalist.component'; @@ -30,6 +30,8 @@ const multiSectionMap = [ { title: 'cat 4', suggestions: [{ name: 'My lol', value: 'lol' }] }, ]; +jest.unmock('@talend/design-system'); + describe('Datalist component', () => { it('should render a input', () => { // when @@ -39,12 +41,14 @@ describe('Datalist component', () => { expect(screen.getByRole('textbox')).toBeInTheDocument(); }); - it('should show all suggestions on focus (even with a value)', () => { + it('should show all suggestions on focus (even with a value)', async () => { + const user = userEvent.setup(); + // given render(); // when - fireEvent.click(screen.getByRole('textbox')); + await user.click(screen.getByRole('textbox')); // then // container.getElementsByClassName(''); @@ -54,12 +58,14 @@ describe('Datalist component', () => { expect(screen.getByTitle('My mdr')).toBeInTheDocument(); }); - it('should show all suggestions on down press (even with a value)', () => { + it('should show all suggestions on down press (even with a value)', async () => { + const user = userEvent.setup(); + // given render(); // when - userEvent.type(screen.getByRole('textbox'), '{down}'); + await user.type(screen.getByRole('textbox'), '{Down}'); // then // container.getElementsByClassName(''); @@ -69,12 +75,14 @@ describe('Datalist component', () => { expect(screen.getByTitle('My mdr')).toBeInTheDocument(); }); - it('should show all suggestions on up press (even with a value)', () => { + it('should show all suggestions on up press (even with a value)', async () => { + const user = userEvent.setup(); + // given render(); // when - userEvent.type(screen.getByRole('textbox'), '{up}'); + await user.type(screen.getByRole('textbox'), '{Up}'); // then // container.getElementsByClassName(''); @@ -84,12 +92,15 @@ describe('Datalist component', () => { expect(screen.getByTitle('My mdr')).toBeInTheDocument(); }); - it('should show suggestions that match filter', () => { + it('should show suggestions that match filter', async () => { + const user = userEvent.setup(); + // given render(); // when - userEvent.type(screen.getByRole('textbox'), 'foo'); + const textbox = screen.getByRole('textbox'); + await user.type(textbox, 'foo'); // then // container.getElementsByClassName(''); @@ -99,13 +110,16 @@ describe('Datalist component', () => { expect(screen.queryByTitle('My mdr')).not.toBeInTheDocument(); }); - it('should show suggestions in group that match filter', () => { + it('should show suggestions in group that match filter', async () => { + const user = userEvent.setup(); + // given const multiSectionProps = { ...props, titleMap: multiSectionMap }; render(); // when - userEvent.type(screen.getByRole('textbox'), 'foo'); + const textbox = screen.getByRole('textbox'); + await user.type(textbox, 'foo'); // then expect(screen.getByTitle('My foo')).toBeInTheDocument(); @@ -114,21 +128,25 @@ describe('Datalist component', () => { expect(screen.queryByTitle('My lol')).not.toBeInTheDocument(); }); - it('should call callback on focus event', () => { + it('should call callback on focus event', async () => { + const user = userEvent.setup(); + // given const onFocus = jest.fn(); render(); const input = screen.getByRole('textbox'); - expect(onFocus).not.toBeCalled(); + expect(onFocus).not.toHaveBeenCalled(); // when - userEvent.click(input); + await user.click(input); // then - expect(onFocus).toBeCalled(); + expect(onFocus).toHaveBeenCalled(); }); - it('should call callback on input live change', () => { + it('should call callback on input live change', async () => { + const user = userEvent.setup(); + // given const onLiveChange = jest.fn(); render( @@ -137,13 +155,13 @@ describe('Datalist component', () => { const input = screen.getByRole('textbox'); // when - userEvent.type(input, 'lo'); + await user.type(input, 'lo'); // then - expect(onLiveChange).toBeCalledWith(expect.anything(), 'lo'); + expect(onLiveChange).toHaveBeenCalledWith(expect.anything(), 'lo'); }); - it('should call callback on blur', () => { + it('should call callback on blur', async () => { // given const onBlur = jest.fn(); render(); @@ -153,86 +171,90 @@ describe('Datalist component', () => { fireEvent.blur(input); // then - expect(onBlur).toBeCalled(); + await waitFor(() => expect(onBlur).toHaveBeenCalled()); }); - it('should close suggestions on blur', () => { + it('should close suggestions on blur', async () => { + const user = userEvent.setup(); + // given - jest.useFakeTimers(); render(); const input = screen.getByRole('textbox'); - fireEvent.click(input); + await user.click(input); expect(screen.getByRole('listbox')).toBeInTheDocument(); // when fireEvent.blur(input); - jest.runAllTimers(); // focus manager // then - expect(screen.queryByRole('listbox')).not.toBeInTheDocument(); - jest.useRealTimers(); + await waitFor(() => expect(screen.queryByRole('listbox')).not.toBeInTheDocument()); }); - it('should close suggestions on enter', () => { + it('should close suggestions on enter', async () => { + const user = userEvent.setup(); + // given render(); const input = screen.getByRole('textbox'); expect(screen.queryByRole('listbox')).not.toBeInTheDocument(); - userEvent.click(input); + await user.click(input); expect(screen.getByRole('listbox')).toBeInTheDocument(); // when - userEvent.type(input, '{enter}'); + await user.type(input, '{Enter}'); // then expect(screen.queryByRole('listbox')).not.toBeInTheDocument(); }); - it('should close suggestions on esc', () => { + it('should close suggestions on esc', async () => { + const user = userEvent.setup(); + // given render(); const input = screen.getByRole('textbox'); - fireEvent.click(input); + await user.click(input); expect(screen.getByRole('listbox')).toBeInTheDocument(); // when - userEvent.type(input, '{esc}'); + await user.type(input, '{Esc}'); // then expect(screen.queryByRole('listbox')).not.toBeInTheDocument(); }); - it('should clear input', () => { + it('should clear input', async () => { + const user = userEvent.setup(); + // given - jest.useFakeTimers(); const onChange = jest.fn(); render(); // when const input = screen.getByRole('textbox'); - userEvent.clear(input); + await user.clear(input); fireEvent.blur(input); - jest.runAllTimers(); // focus manager // then - expect(onChange).toBeCalledWith(expect.anything(), { value: '' }); - jest.useRealTimers(); + await waitFor(() => expect(onChange).toHaveBeenCalledWith(expect.anything(), { value: '' })); }); - it('should reset previous value on ESC keydown', () => { + it('should reset previous value on ESC keydown', async () => { + const user = userEvent.setup(); + // given const onChange = jest.fn(); render(); // when const input = screen.getByRole('textbox'); - userEvent.type(input, 'whatever{esc}'); + await user.type(input, 'whatever{Esc}'); // then - expect(onChange).not.toBeCalled(); + expect(onChange).not.toHaveBeenCalled(); expect(input).toHaveValue('My foo'); }); @@ -270,7 +292,9 @@ describe('Datalist component', () => { expect(screen.getByRole('textbox')).toHaveValue('My foo updated'); }); - it('should keep filter when titleMap is updated', () => { + it('should keep filter when titleMap is updated', async () => { + const user = userEvent.setup(); + // given const testProps = { id: 'my-datalist', @@ -281,14 +305,16 @@ describe('Datalist component', () => { const { rerender } = render(); // when - userEvent.type(screen.getByRole('textbox'), 'a'); + await user.type(screen.getByRole('textbox'), 'a'); rerender(); // then expect(screen.getByRole('textbox')).toHaveValue('a'); }); - it('should keep filter when titleMap is updated and value is empty', () => { + it('should keep filter when titleMap is updated and value is empty', async () => { + const user = userEvent.setup(); + // given const testProps = { id: 'my-datalist', @@ -299,7 +325,7 @@ describe('Datalist component', () => { const { rerender } = render(); // when - userEvent.type(screen.getByRole('textbox'), 'a'); + await user.type(screen.getByRole('textbox'), 'a'); rerender(); // then @@ -340,13 +366,15 @@ describe('Datalist component', () => { expect(screen.getByRole('textbox')).toHaveValue(newTitleMap[0].name); }); - it('should set highlight on current value suggestion', () => { + it('should set highlight on current value suggestion', async () => { + const user = userEvent.setup(); + // given render(); const input = screen.getByRole('textbox'); // when - userEvent.click(input); + await user.click(input); // then expect(screen.getByTitle('My foo')).toHaveClass('theme-selected'); @@ -355,186 +383,205 @@ describe('Datalist component', () => { }); describe('non restricted mode (default)', () => { - it('should persist known value on blur', () => { + it('should persist known value on blur', async () => { + const user = userEvent.setup(); + // given - jest.useFakeTimers(); const onChange = jest.fn(); render(); - expect(onChange).not.toBeCalled(); + expect(onChange).not.toHaveBeenCalled(); // when const input = screen.getByRole('textbox'); - userEvent.type(input, 'foo'); + await user.type(input, 'foo'); fireEvent.blur(input); - jest.runAllTimers(); // focus manager // then - expect(onChange).toBeCalledWith(expect.anything(), { value: 'foo' }); - jest.useRealTimers(); + await waitFor(() => + expect(onChange).toHaveBeenCalledWith(expect.anything(), { value: 'foo' }), + ); }); - it('should persist unknown value on blur', () => { + it('should persist unknown value on blur', async () => { + const user = userEvent.setup(); + // given - jest.useFakeTimers(); const onChange = jest.fn(); render(); // when const input = screen.getByRole('textbox'); - userEvent.type(input, 'not a known value'); + await user.type(input, 'not a known value'); fireEvent.blur(input); - jest.runAllTimers(); // focus manager // then - expect(onChange).toBeCalledWith(expect.anything(), { value: 'not a known value' }); - jest.useRealTimers(); + await waitFor(() => + expect(onChange).toHaveBeenCalledWith(expect.anything(), { value: 'not a known value' }), + ); }); - it('should persist known value on enter', () => { + it('should persist known value on enter', async () => { + const user = userEvent.setup(); + // given const onChange = jest.fn(); render(); - expect(onChange).not.toBeCalled(); + expect(onChange).not.toHaveBeenCalled(); // when const input = screen.getByRole('textbox'); - userEvent.type(input, 'foo{enter}'); + await user.type(input, 'foo{enter}'); // then - expect(onChange).toBeCalledWith(expect.anything(), { value: 'foo' }); + expect(onChange).toHaveBeenCalledWith(expect.anything(), { value: 'foo' }); }); - it('should persist unknown value on enter', () => { + it('should persist unknown value on enter', async () => { + const user = userEvent.setup(); + // given const onChange = jest.fn(); render(); // when const input = screen.getByRole('textbox'); - userEvent.type(input, 'not a known value{enter}'); + await user.type(input, 'not a known value{enter}'); // then - expect(onChange).toBeCalledWith(expect.anything(), { value: 'not a known value' }); + expect(onChange).toHaveBeenCalledWith(expect.anything(), { value: 'not a known value' }); }); }); describe('allowAddNewElements mode', () => { - it('should persist new value on blur', () => { + it('should persist new value on blur', async () => { + const user = userEvent.setup(); + // given - jest.useFakeTimers(); const onChange = jest.fn(); render(); - expect(onChange).not.toBeCalled(); + expect(onChange).not.toHaveBeenCalled(); const input = screen.getByRole('textbox'); // when - userEvent.type(input, 'not there'); + await user.type(input, 'not there'); expect(screen.getByTitle('not there (new)')).toBeInTheDocument(); fireEvent.blur(input); - jest.runAllTimers(); // focus manager - // // then - expect(onChange).toBeCalledWith(expect.anything(), { value: 'not there' }); - jest.useRealTimers(); + // then + await waitFor(() => + expect(onChange).toHaveBeenCalledWith(expect.anything(), { value: 'not there' }), + ); }); }); describe('restricted mode', () => { - it('should persist known value on blur', () => { + it('should persist known value on blur', async () => { + const user = userEvent.setup(); + // given - jest.useFakeTimers(); const onChange = jest.fn(); render(); - expect(onChange).not.toBeCalled(); + expect(onChange).not.toHaveBeenCalled(); const input = screen.getByRole('textbox'); // when - userEvent.type(input, 'foo'); + await user.type(input, 'foo'); fireEvent.blur(input); - jest.runAllTimers(); // focus manager // then - expect(onChange).toBeCalledWith(expect.anything(), { value: 'foo' }); - jest.useRealTimers(); + await waitFor(() => + expect(onChange).toHaveBeenCalledWith(expect.anything(), { value: 'foo' }), + ); }); - it('should reset unknown value on blur', () => { + it('should reset unknown value on blur', async () => { + const user = userEvent.setup(); + // given - jest.useFakeTimers(); const onChange = jest.fn(); render(); // when const input = screen.getByRole('textbox'); - userEvent.type(input, 'not a known value'); + await user.type(input, 'not a known value'); fireEvent.blur(input); - jest.runAllTimers(); // focus manager // then - expect(onChange).not.toBeCalled(); - expect(input).toHaveValue('My foo'); - jest.useRealTimers(); + await Promise.all([ + waitFor(() => expect(onChange).not.toHaveBeenCalled()), + waitFor(() => expect(input).toHaveValue('My foo')), + ]); }); - it('should persist known value on enter', () => { + it('should persist known value on enter', async () => { + const user = userEvent.setup(); + // given const onChange = jest.fn(); render(); - expect(onChange).not.toBeCalled(); + expect(onChange).not.toHaveBeenCalled(); // when const input = screen.getByRole('textbox'); - userEvent.type(input, 'foo{enter}'); + await user.type(input, 'foo{Enter}'); // then - expect(onChange).toBeCalledWith(expect.anything(), { value: 'foo' }); + await waitFor(() => + expect(onChange).toHaveBeenCalledWith(expect.anything(), { value: 'foo' }), + ); }); - it('should reset unknown value on enter', () => { + it('should reset unknown value on enter', async () => { + const user = userEvent.setup(); + // given const onChange = jest.fn(); render(); // when const input = screen.getByRole('textbox'); - userEvent.type(input, 'not a known value{enter}'); + await user.type(input, 'not a known value{Enter}'); // then - expect(onChange).not.toBeCalled(); - expect(input).toHaveValue('My foo'); + await Promise.all([ + waitFor(() => expect(onChange).not.toHaveBeenCalled()), + waitFor(() => expect(input).toHaveValue('My foo')), + ]); }); - it('should persist empty value on enter', () => { + it('should persist empty value on enter', async () => { + const user = userEvent.setup(); + // given const onChange = jest.fn(); render(); - expect(onChange).not.toBeCalled(); + expect(onChange).not.toHaveBeenCalled(); // when const input = screen.getByRole('textbox'); - userEvent.clear(input); - userEvent.type(input, '{enter}'); + await user.clear(input); + await user.type(input, '{Enter}'); // then - expect(onChange).toBeCalledWith(expect.anything(), { value: '' }); + await waitFor(() => expect(onChange).toHaveBeenCalledWith(expect.anything(), { value: '' })); }); - it('should persist empty value on blur', () => { + it('should persist empty value on blur', async () => { + const user = userEvent.setup(); + // given - jest.useFakeTimers(); const onChange = jest.fn(); render(); - expect(onChange).not.toBeCalled(); + expect(onChange).not.toHaveBeenCalled(); // when const input = screen.getByRole('textbox'); - userEvent.clear(input); + await user.clear(input); + fireEvent.blur(input); - jest.runAllTimers(); // focus manager // then - expect(onChange).toBeCalledWith(expect.anything(), { value: '' }); - jest.useRealTimers(); + await waitFor(() => expect(onChange).toHaveBeenCalledWith(expect.anything(), { value: '' })); }); }); }); diff --git a/packages/components/src/Datalist/Datalist.module.scss b/packages/components/src/Datalist/Datalist.module.scss index 1ceb3c64c4..32fce61d2e 100644 --- a/packages/components/src/Datalist/Datalist.module.scss +++ b/packages/components/src/Datalist/Datalist.module.scss @@ -1,8 +1,8 @@ -@use '~@talend/bootstrap-theme/src/theme/guidelines' as *; -@use '~@talend/design-tokens/lib/tokens'; +@use '@talend/bootstrap-theme/src/theme/guidelines' as *; +@use '@talend/design-tokens/lib/tokens' as tokens; -$tc-datalist-items-max-height: 32rem !default; -$tc-datalist-item-height: 4rem !default; +$tc-datalist-items-max-height: 20rem !default; +$tc-datalist-item-height: 2.5rem !default; .tc-datalist-form { width: 100%; diff --git a/packages/components/src/DateTimePickers/Date/Input/Input.component.js b/packages/components/src/DateTimePickers/Date/Input/Input.component.js index 4304d88992..b292059919 100644 --- a/packages/components/src/DateTimePickers/Date/Input/Input.component.js +++ b/packages/components/src/DateTimePickers/Date/Input/Input.component.js @@ -1,9 +1,12 @@ import { useContext } from 'react'; -import PropTypes from 'prop-types'; import DebounceInput from 'react-debounce-input'; -import { DateContext } from '../Context'; +import PropTypes from 'prop-types'; + +import { Form } from '@talend/design-system'; + import InputSizer from '../../shared/InputSizer'; +import { DateContext } from '../Context'; function Input(props) { const { value, inputManagement } = useContext(DateContext); @@ -14,9 +17,9 @@ function Input(props) { {width => ( { const props = JSON.parse(screen.getByTestId('DebounceInput').dataset.props); expect(props).toEqual({ autoComplete: 'off', - className: 'form-control', + hideLabel: true, debounceTimeout: 300, - type: 'text', value: '2007-01-02', style: { width: 300 }, 'aria-labelledby': 'labelId', diff --git a/packages/components/src/DateTimePickers/Date/Manager/Manager.component.test.js b/packages/components/src/DateTimePickers/Date/Manager/Manager.component.test.js index 74e27f626f..f7d6030a63 100644 --- a/packages/components/src/DateTimePickers/Date/Manager/Manager.component.test.js +++ b/packages/components/src/DateTimePickers/Date/Manager/Manager.component.test.js @@ -1,8 +1,9 @@ +/* eslint-disable react/prop-types */ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import Manager from './Manager.component'; import { DateContext } from '../Context'; +import Manager from './Manager.component'; const DEFAULT_ID = 'DEFAULT_ID'; @@ -50,7 +51,7 @@ describe('Date.Manager', () => { expect(props).toEqual({ value: { textInput: '2017-04-04', - date: '2017-04-03T22:00:00.000Z', + date: '2017-04-04T00:00:00.000Z', }, inputManagement: { placeholder: 'YYYY-MM-DD', @@ -79,7 +80,7 @@ describe('Date.Manager', () => { name: 'should init state from props', initialDate: new Date(2015, 3, 4), expectedTextInput: '2015-04-04', - expectedDate: '2015-04-03T22:00:00.000Z', + expectedDate: '2015-04-04T00:00:00.000Z', }, ])('$name', ({ initialDate, expectedTextInput, expectedDate }) => { // when @@ -115,7 +116,7 @@ describe('Date.Manager', () => { initialDate: new Date(), newDate: new Date(2015, 3, 4), expectedTextInput: '2015-04-04', - expectedDate: '2015-04-03T22:00:00.000Z', + expectedDate: '2015-04-04T00:00:00.000Z', }, ])('$name', ({ initialDate, newDate, expectedTextInput, expectedDate }) => { // given @@ -176,7 +177,7 @@ describe('Date.Manager', () => { { name: 'with valid date', textInput: '2015-01-15', - expectedDate: '2015-01-14T23:00:00.000Z', + expectedDate: '2015-01-15T00:00:00.000Z', }, { name: 'with invalid date', @@ -191,10 +192,12 @@ describe('Date.Manager', () => { { name: 'with custom date format', textInput: '15/01/2015', - expectedDate: '2015-01-14T23:00:00.000Z', + expectedDate: '2015-01-15T00:00:00.000Z', dateFormat: 'DD/MM/YYYY', }, ])('$name', async ({ textInput, expectedDate, dateFormat }) => { + const user = userEvent.setup(); + // given render( @@ -203,8 +206,8 @@ describe('Date.Manager', () => { ); // when - await userEvent.click(screen.getByTestId('DateConsumerDivInput')); - await userEvent.keyboard(textInput); + await user.click(screen.getByTestId('DateConsumerDivInput')); + await user.keyboard(textInput); // then const props = JSON.parse(screen.getByTestId('DateConsumerDiv').dataset.props); @@ -215,6 +218,8 @@ describe('Date.Manager', () => { }); it('should trigger props.onChange with valid date', async () => { + const user = userEvent.setup(); + // given const onChange = jest.fn(); render( @@ -222,14 +227,14 @@ describe('Date.Manager', () => { , ); - expect(onChange).not.toBeCalled(); + expect(onChange).not.toHaveBeenCalled(); // when - await userEvent.click(screen.getByTestId('DateConsumerDivInput')); - await userEvent.keyboard('2015-01-15'); + await user.click(screen.getByTestId('DateConsumerDivInput')); + await user.keyboard('2015-01-15'); // then - expect(onChange).toBeCalledWith(expect.anything(), { + expect(onChange).toHaveBeenCalledWith(expect.anything(), { date: new Date(2015, 0, 15), origin: 'INPUT', textInput: '2015-01-15', @@ -239,6 +244,8 @@ describe('Date.Manager', () => { }); it('should trigger props.onChange with invalid date', async () => { + const user = userEvent.setup(); + // given const onChange = jest.fn(); render( @@ -246,14 +253,14 @@ describe('Date.Manager', () => { , ); - expect(onChange).not.toBeCalled(); + expect(onChange).not.toHaveBeenCalled(); // when - await userEvent.click(screen.getByTestId('DateConsumerDivInput')); - await userEvent.keyboard('2015-01-15'); + await user.click(screen.getByTestId('DateConsumerDivInput')); + await user.keyboard('2015-01-15'); // then - expect(onChange).toBeCalled(); + expect(onChange).toHaveBeenCalled(); const args = onChange.mock.calls[0]; expect(args[0]).toMatchObject({ type: 'change', @@ -284,6 +291,8 @@ describe('Date.Manager', () => { expectedTextInput: '15/01/2015', }, ])('$name', async ({ date, expectedTextInput, dateFormat }) => { + const user = userEvent.setup(); + // given render( @@ -292,7 +301,7 @@ describe('Date.Manager', () => { ); // when - await userEvent.click(screen.getByRole('button')); + await user.click(screen.getByRole('button')); // then const props = JSON.parse(screen.getByTestId('DateConsumerDiv').dataset.props); @@ -300,24 +309,25 @@ describe('Date.Manager', () => { }); it('should trigger props.onChange with valid date', async () => { + const user = userEvent.setup(); + // given const onChange = jest.fn(); - const event = { target: {}, preventDefault: () => {} }; render( , ); - expect(onChange).not.toBeCalled(); + expect(onChange).not.toHaveBeenCalled(); // when - await userEvent.click(screen.getByTestId('DateConsumerDivInput')); - await userEvent.keyboard('2015-01-15'); - await userEvent.click(screen.getByRole('button')); + await user.click(screen.getByTestId('DateConsumerDivInput')); + await user.keyboard('2015-01-15'); + await user.click(screen.getByRole('button')); // then - expect(onChange).toBeCalledWith( + expect(onChange).toHaveBeenCalledWith( expect.anything({ type: 'change', target: expect.anything({ diff --git a/packages/components/src/DateTimePickers/Date/Picker/Picker.component.test.js b/packages/components/src/DateTimePickers/Date/Picker/Picker.component.test.js index 9fa9f835d5..551e0eea40 100644 --- a/packages/components/src/DateTimePickers/Date/Picker/Picker.component.test.js +++ b/packages/components/src/DateTimePickers/Date/Picker/Picker.component.test.js @@ -32,7 +32,7 @@ describe('Date.Picker', () => { expect(props).toMatchObject({ manageFocus: true, other: 'custom props', - selectedDate: '2007-01-01T23:00:00.000Z', + selectedDate: '2007-01-02T00:00:00.000Z', useUTC: false, }); }); diff --git a/packages/components/src/DateTimePickers/Date/date-extraction.js b/packages/components/src/DateTimePickers/Date/date-extraction.js index 58c1cecccb..2bd5be26f5 100644 --- a/packages/components/src/DateTimePickers/Date/date-extraction.js +++ b/packages/components/src/DateTimePickers/Date/date-extraction.js @@ -1,7 +1,8 @@ -import format from 'date-fns/format'; -import getDate from 'date-fns/get_date'; -import lastDayOfMonth from 'date-fns/last_day_of_month'; -import setDate from 'date-fns/set_date'; +import { format } from 'date-fns/format'; +import { getDate } from 'date-fns/getDate'; +import { lastDayOfMonth } from 'date-fns/lastDayOfMonth'; +import { setDate } from 'date-fns/setDate'; + import { date as dateUtils } from '@talend/utils'; import getErrorMessage from '../shared/error-messages'; @@ -47,7 +48,7 @@ function isDateValid(date, options) { * @param {Object} options */ function dateToStr(date, { dateFormat }) { - return format(date, dateFormat); + return format(date, dateUtils.formatToUnicode(dateFormat)); } function convertDateToTimezone(date, { useUTC, timezone }) { @@ -248,7 +249,7 @@ function extractFromDate(date, options) { return { localDate: date, date: convertDateToTimezone(date, options), - textInput: format(date, options.dateFormat), + textInput: format(date, dateUtils.formatToUnicode(options.dateFormat)), errors: [], errorMessage: null, }; diff --git a/packages/components/src/DateTimePickers/Date/date-extraction.test.js b/packages/components/src/DateTimePickers/Date/date-extraction.test.js index 1e3d2c7bb4..f1b4c5c065 100644 --- a/packages/components/src/DateTimePickers/Date/date-extraction.test.js +++ b/packages/components/src/DateTimePickers/Date/date-extraction.test.js @@ -1,12 +1,13 @@ -import isAfter from 'date-fns/is_after'; -import subHours from 'date-fns/sub_hours'; +import { isAfter } from 'date-fns/isAfter'; +import { subHours } from 'date-fns/subHours'; + import { checkSupportedDateFormat, checkSupportedTimezone, extractDate, - extractPartsFromTextInput, - extractPartsFromDate, extractDateOnly, + extractPartsFromDate, + extractPartsFromTextInput, } from './date-extraction'; describe('Date extraction', () => { @@ -273,7 +274,7 @@ describe('Date extraction', () => { // then expect(parts).toEqual({ localDate: new Date(2018, 11, 25), - date: subHours(new Date(2018, 11, 25), 8), + date: subHours(new Date(2018, 11, 25), 9), textInput, errorMessage: null, errors: [], @@ -325,7 +326,7 @@ describe('Date extraction', () => { const date = extractDateOnly(datetime, options); // then - expect(date).toEqual(new Date(2019, 8, 25)); + expect(date).toEqual(new Date(2019, 8, 26)); }); it('should extract date when timezone provided', () => { // given diff --git a/packages/components/src/DateTimePickers/DateRange/Input/Input.component.js b/packages/components/src/DateTimePickers/DateRange/Input/Input.component.js index b13eb9b5f1..e447fdf7ce 100644 --- a/packages/components/src/DateTimePickers/DateRange/Input/Input.component.js +++ b/packages/components/src/DateTimePickers/DateRange/Input/Input.component.js @@ -1,10 +1,13 @@ -import { useContext, forwardRef } from 'react'; -import PropTypes from 'prop-types'; -import omit from 'lodash/omit'; +import { forwardRef, useContext } from 'react'; import DebounceInput from 'react-debounce-input'; -import { DateRangeContext } from '../Context'; +import omit from 'lodash/omit'; +import PropTypes from 'prop-types'; + +import { Form } from '@talend/design-system'; + import InputSizer from '../../shared/InputSizer'; +import { DateRangeContext } from '../Context'; const OMIT_INPUT_PROPS = ['date', 'onChange', 'onFocus', 'label', 'minWidth']; @@ -15,16 +18,14 @@ const Input = forwardRef((props, ref) => { return (
    - + {label && {label}} {width => ( { // then const input = screen.getByTestId('debounce'); expect(input).toHaveAttribute('autocomplete', 'off'); - expect(input).toHaveClass('form-control'); expect(input).toHaveAttribute('debouncetimeout', '300'); - expect(input).toHaveAttribute('type', 'text'); expect(input).toHaveAttribute('placeholder', 'YYYY-MM-DD'); expect(input).toHaveValue('2019-10-11'); expect(input).toHaveStyle('width: 300px;'); diff --git a/packages/components/src/DateTimePickers/DateRange/Manager/Manager.component.js b/packages/components/src/DateTimePickers/DateRange/Manager/Manager.component.js index c88056f72e..ba3a56ec22 100644 --- a/packages/components/src/DateTimePickers/DateRange/Manager/Manager.component.js +++ b/packages/components/src/DateTimePickers/DateRange/Manager/Manager.component.js @@ -1,15 +1,15 @@ -import { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import isBefore from 'date-fns/is_before'; +import { useEffect, useState } from 'react'; -import { DateRangeContext } from '../Context'; -import getErrorMessage from '../../shared/error-messages'; +import { isBefore } from 'date-fns/isBefore'; +import PropTypes from 'prop-types'; import { extractDate, extractFromDate, extractPartsFromTextInput, } from '../../Date/date-extraction'; +import getErrorMessage from '../../shared/error-messages'; +import { DateRangeContext } from '../Context'; export function DateRangePickerException(code, message) { this.message = getErrorMessage(message); diff --git a/packages/components/src/DateTimePickers/DateRange/Manager/Manager.component.test.js b/packages/components/src/DateTimePickers/DateRange/Manager/Manager.component.test.js index cde6bdbdfa..ca73b5af30 100644 --- a/packages/components/src/DateTimePickers/DateRange/Manager/Manager.component.test.js +++ b/packages/components/src/DateTimePickers/DateRange/Manager/Manager.component.test.js @@ -2,8 +2,8 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import Manager from './Manager.component'; import { DateRangeContext } from '../Context'; +import Manager from './Manager.component'; const DEFAULT_ID = 'DEFAULT_ID'; @@ -51,11 +51,11 @@ describe('DateRange.Manager', () => { const consumer = screen.getByTestId('DateRangeConsumerDiv'); const props = JSON.parse(consumer.getAttribute('data-props')); expect(props.startDate).toEqual({ - value: '2017-04-03T22:00:00.000Z', + value: '2017-04-04T00:00:00.000Z', textInput: '2017-04-04', }); expect(props.endDate).toEqual({ - value: '2017-04-09T22:00:00.000Z', + value: '2017-04-10T00:00:00.000Z', textInput: '2017-04-10', }); }); @@ -289,15 +289,15 @@ describe('DateRange.Manager', () => { expectedDate: undefined, }, { - name: 'startDate - with empty string', + name: 'startDate - with space', field: 'startDate', - textInput: '', + textInput: ' ', expectedDate: undefined, }, { - name: 'endDate - with empty string', + name: 'endDate - with space', field: 'endDate', - textInput: '', + textInput: ' ', expectedDate: undefined, }, { @@ -315,6 +315,8 @@ describe('DateRange.Manager', () => { dateFormat: 'DD/MM/YYYY', }, ])('$name', async ({ field, textInput, expectedDate, dateFormat }) => { + const user = userEvent.setup(); + // given // let onChange = 'onEndChange'; // if (field === 'startDate') { @@ -345,8 +347,8 @@ describe('DateRange.Manager', () => { ); // when - await userEvent.click(screen.getByTestId(field)); - await userEvent.keyboard(textInput); + await user.click(screen.getByTestId(field)); + await user.keyboard(textInput); // then const props = JSON.parse(screen.getByTestId('DateRangeConsumerDiv').dataset.props); @@ -373,6 +375,7 @@ describe('DateRange.Manager', () => { ])( '$name', async ({ field, inputText, expectedStartDate, expectedEndDate, expectedOrigin }) => { + const user = userEvent.setup(); // given const onChange = jest.fn(); render( @@ -380,14 +383,14 @@ describe('DateRange.Manager', () => { , ); - expect(onChange).not.toBeCalled(); + expect(onChange).not.toHaveBeenCalled(); // when - await userEvent.click(screen.getByTestId(field)); - await userEvent.keyboard(inputText); + await user.click(screen.getByTestId(field)); + await user.keyboard(inputText); // then - expect(onChange).toBeCalledWith(expect.anything(), { + expect(onChange).toHaveBeenCalledWith(expect.anything(), { startDate: expectedStartDate, endDate: expectedEndDate, errors: [], @@ -417,6 +420,8 @@ describe('DateRange.Manager', () => { ])( '$name', async ({ field, inputText, errors, errorMessage, expectedStartDate, expectedEndDate }) => { + const user = userEvent.setup(); + // given const onChange = jest.fn(); @@ -425,13 +430,13 @@ describe('DateRange.Manager', () => { , ); - expect(onChange).not.toBeCalled(); + expect(onChange).not.toHaveBeenCalled(); //when - await userEvent.click(screen.getByTestId(field)); - await userEvent.keyboard(inputText); + await user.click(screen.getByTestId(field)); + await user.keyboard(inputText); // then - expect(onChange).toBeCalled(); + expect(onChange).toHaveBeenCalled(); const args = onChange.mock.calls[0]; expect(args[1].errorMessage).toBe(errorMessage); expect(args[1].errors).toEqual(errors); @@ -469,6 +474,7 @@ describe('DateRange.Manager', () => { expectedTextInput: '15/01/2015', }, ])('$name', async ({ field, date, expectedTextInput, dateFormat }) => { + const user = userEvent.setup(); // given const pickerHandler = field === 'endDate' ? 'onEndChange' : 'onStartChange'; render( @@ -481,7 +487,7 @@ describe('DateRange.Manager', () => { , ); // when - await userEvent.click(screen.getByTestId(`picker-${field}`)); + await user.click(screen.getByTestId(`picker-${field}`)); const props = JSON.parse(screen.getByTestId('DateRangeConsumerDiv').dataset.props); expect(props[field].textInput).toBe(expectedTextInput); expect(props[field].value).toEqual(date?.toISOString()); @@ -504,6 +510,7 @@ describe('DateRange.Manager', () => { ])( '$name', async ({ field, selectedDate, expectedStartDate, expectedEndDate, expectedOrigin }) => { + const user = userEvent.setup(); // given const pickerHandler = field === 'endDate' ? 'onEndChange' : 'onStartChange'; const onChange = jest.fn(); @@ -516,13 +523,13 @@ describe('DateRange.Manager', () => { /> , ); - expect(onChange).not.toBeCalled(); + expect(onChange).not.toHaveBeenCalled(); // when - await userEvent.click(screen.getByTestId(`picker-${field}`)); + await user.click(screen.getByTestId(`picker-${field}`)); // then - expect(onChange).toBeCalledWith( + expect(onChange).toHaveBeenCalledWith( expect.anything({ type: 'change', }), diff --git a/packages/components/src/DateTimePickers/DateRange/Picker/Picker.component.test.js b/packages/components/src/DateTimePickers/DateRange/Picker/Picker.component.test.js index c32b7888b7..4dcc4d47f1 100644 --- a/packages/components/src/DateTimePickers/DateRange/Picker/Picker.component.test.js +++ b/packages/components/src/DateTimePickers/DateRange/Picker/Picker.component.test.js @@ -1,6 +1,7 @@ /* eslint-disable react/prop-types */ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; + import { DateRangeContext } from '../Context'; import Picker from './Picker.component'; @@ -51,12 +52,12 @@ describe('DateRange.Picker', () => { , ); - expect(managerValue.pickerManagement.onStartChange).not.toBeCalled(); + expect(managerValue.pickerManagement.onStartChange).not.toHaveBeenCalled(); // when await userEvent.click(screen.getByLabelText('Monday 01 January 2007')); // then - expect(managerValue.pickerManagement.onStartChange).toBeCalled(); + expect(managerValue.pickerManagement.onStartChange).toHaveBeenCalled(); }); }); diff --git a/packages/components/src/DateTimePickers/DateRange/Picker/__snapshots__/Picker.component.test.js.snap b/packages/components/src/DateTimePickers/DateRange/Picker/__snapshots__/Picker.component.test.js.snap index a289c4dc1d..85d7bb5ca4 100644 --- a/packages/components/src/DateTimePickers/DateRange/Picker/__snapshots__/Picker.component.test.js.snap +++ b/packages/components/src/DateTimePickers/DateRange/Picker/__snapshots__/Picker.component.test.js.snap @@ -16,35 +16,167 @@ exports[`DateRange.Picker should render 1`] = ` class="theme-element-container theme-left" > + > + Go to previous month +
    - +
    + +
    + +
    + > + Go to next month +
    M + T + W + T + F + S + S + @@ -702,8 +855,11 @@ exports[`DateRange.Picker should render 1`] = `
    + +