From bd152f9d410471a02ec66bd43a2694a8cfc31c1e Mon Sep 17 00:00:00 2001 From: dlcaldeira <124570762+dlcaldeira@users.noreply.github.com> Date: Thu, 30 Nov 2023 08:47:05 +0000 Subject: [PATCH] feat(tdc-7378): New enumeration component --- .changeset/orange-seahorses-talk.md | 5 + fork/react-bootstrap/src/Carousel.test.js | 601 ++++++++--------- fork/react-bootstrap/src/CloseButton.test.js | 139 ++-- fork/react-bootstrap/src/ControlLabel.test.js | 1 + fork/react-bootstrap/src/Dropdown.js | 631 +++++++++--------- fork/react-bootstrap/src/Dropdown.test.js | 7 +- fork/react-bootstrap/src/DropdownMenu.js | 237 +++---- fork/react-bootstrap/src/DropdownMenu.test.js | 2 +- fork/react-bootstrap/src/Nav.js | 5 +- fork/react-bootstrap/test/DropdownMenuSpec.js | 2 +- fork/react-bootstrap/test/DropdownSpec.js | 2 +- fork/react-bootstrap/test/MenuItemSpec.js | 2 +- fork/react-bootstrap/test/NavSpec.js | 2 +- .../src/Gesture/withCalendarGesture.test.tsx | 4 +- .../a11y/src/Gesture/withCalendarGesture.tsx | 9 +- .../Gesture/withDynamicListGesture.test.js | 2 + .../src/Gesture/withDynamicListGesture.tsx | 1 + .../a11y/src/Gesture/withListGesture.test.js | 3 +- .../src/Gesture/withMonthCalendarGesture.tsx | 9 +- .../a11y/src/Gesture/withTreeGesture.test.js | 3 +- packages/a11y/src/Gesture/withTreeGesture.tsx | 3 +- .../src/middleware/smartWebsocket.test.js | 5 +- packages/cmf/__tests__/App.test.js | 8 +- packages/cmf/__tests__/bootstrap.test.js | 7 +- packages/cmf/__tests__/cmfConnect.test.js | 15 +- packages/cmf/__tests__/expression.test.js | 1 + .../ActionIntercom/Intercom.component.test.js | 1 + .../src/ActionList/ActionList.test.js | 1 + .../Actions/ActionButton/ActionButton.test.js | 3 +- .../ActionDropdown/ActionDropdown.test.js | 4 +- .../ActionIconToggle.component.test.js | 1 + .../ActionSplitDropdown.test.js | 2 + .../src/AppGuidedTour/AppGuidedTour.test.js | 1 + .../AppSwitcher/AppSwitcher.component.test.js | 3 +- .../BadgeDelete/BadgeDelete.component.test.js | 3 +- .../BadgeDropdown.component.test.js | 3 +- .../src/Breadcrumbs/Breadcrumbs.test.js | 3 +- .../CollapsiblePanel/CollapsiblePanel.test.js | 1 + .../src/ConfirmDialog/ConfirmDialog.test.js | 3 +- .../Headers/TreeHeader/TreeHeader.test.js | 1 + .../Managers/TreeManager/TreeManager.test.js | 3 +- .../Branch/RecordsViewerBranch.component.js | 5 +- .../Branch/RecordsViewerBranch.test.js | 2 + .../CellRenderer/RecordsCellRenderer.test.js | 1 + .../src/Datalist/Datalist.component.js | 17 +- .../Date/Manager/Manager.component.test.js | 3 +- .../Manager/Manager.component.test.js | 2 +- .../DateRange/Picker/Picker.component.test.js | 1 + .../Manager/Manager.component.test.js | 2 +- .../Manager/Manager.component.test.js | 2 +- .../InputDateTimeRangePicker.component.js | 11 +- .../DateTime/Input/Input.component.test.js | 2 +- .../Manager/Manager.component.test.js | 6 +- .../InputDateTimePicker.component.js | 10 +- .../InputDateTimePicker.component.test.js | 2 +- .../pickers/DatePicker/DatePicker.test.js | 3 +- .../DateTimePicker/DateTimePicker.test.js | 5 +- .../pickers/MonthPicker/MonthPicker.test.js | 2 +- .../pickers/TimePicker/TimePicker.test.js | 5 +- .../pickers/YearPicker/YearPicker.test.js | 2 +- .../views/MonthYearView/MonthYearView.test.js | 1 + .../Time/Manager/Manager.component.test.js | 4 +- .../CalendarPicker/CalendarPicker.test.js | 6 +- .../pickers/DatePicker/DatePicker.test.js | 3 +- .../pickers/MonthPicker/MonthPicker.test.js | 3 +- .../TimePicker/TimePicker.component.test.js | 2 +- .../pickers/YearPicker/YearPicker.test.js | 5 +- .../views/DateView/DateView.test.js | 2 +- .../views/MonthYearView/MonthYearView.test.js | 3 +- packages/components/src/Dialog/Dialog.test.js | 2 + .../src/EditableText/InlineForm.component.js | 7 +- .../EditableText/InlineForm.component.test.js | 3 +- .../PlainTextTitle.component.test.js | 3 +- .../src/Enumeration/Header/Header.test.js | 1 + .../Enumeration/Header/HeaderInput.test.js | 2 +- .../Enumeration/Header/headerSelected.test.js | 2 +- .../src/Enumeration/Items/Item/Item.test.js | 4 +- .../Items/Item/ItemEdit.component.js | 10 +- .../Enumeration/Items/Item/ItemEdit.test.js | 2 +- .../src/FilterBar/FilterBar.component.js | 12 +- .../src/FilterBar/FilterBar.test.js | 2 +- .../src/HeaderBar/HeaderBar.test.js | 1 + .../ColumnChooser.component.test.js | 7 +- .../ListDisplayMode.component.test.js | 6 +- .../Manager/ListManager.component.test.js | 5 +- .../hooks/useCollectionSelection.hook.test.js | 3 +- .../hooks/useCollectionSort.hook.test.js | 5 +- .../SortBy/SortBy.component.test.js | 4 +- .../TextFilter/TextFilter.component.test.js | 7 +- .../ListToVirtualizedList.test.js | 5 +- .../ColumnChooser.component.test.js | 3 +- .../ColumnChooserBody.component.test.js | 6 +- .../RowCheckbox/RowCheckbox.component.test.js | 3 +- ...SelectAllColumnsCheckbox.component.test.js | 3 +- .../DisplayModeToggle.test.js | 3 +- .../Toolbar/Pagination/Pagination.test.js | 3 +- .../Toolbar/SelectSortBy/SelectSortBy.test.js | 3 +- .../src/ListView/Header/Header.test.js | 3 +- .../src/ListView/Header/headerInput.test.js | 3 +- .../src/MultiSelect/MultiSelect.container.js | 18 +- .../src/Notification/Notification.test.js | 2 +- .../ObjectViewer/JSONLike/JSONLike.test.js | 6 +- .../src/OverlayTrigger/OverlayTrigger.test.js | 1 + .../src/PieChart/PieChartButton.test.js | 4 +- .../src/RadarChart/RadarChart.test.js | 1 + .../src/RatioBar/RatioBar.component.test.js | 1 + .../RatioBar/RatioBarComposition.component.js | 3 + .../NameFilter/NameFilter.snap.test.js | 3 +- .../Toolbar/NameFilter/NameFilter.test.js | 3 +- .../OrderChooser/OrderChooser.test.js | 2 +- .../Toolbar/SortOptions/SortOptions.test.js | 5 +- .../Toolbar/StateFilter/StateFilter.test.js | 3 +- .../src/SidePanel/SidePanel.test.js | 3 +- .../components/src/TabBar/TabBar.component.js | 20 +- packages/components/src/TabBar/TabBar.test.js | 5 +- .../LabelToggle/LabelToggleComponent.test.js | 1 + packages/components/src/Toggle/Toggle.test.js | 1 + .../src/TooltipTrigger/TooltipTrigger.test.js | 3 +- .../TreeViewItem/TreeViewItem.test.js | 1 + .../src/Typeahead/Typeahead.component.js | 17 +- .../src/Typeahead/Typeahead.test.js | 3 +- .../CellCheckbox/CellCheckbox.test.js | 1 + .../CellTitle/CellTitleActions.test.js | 5 +- .../CellTitle/CellTitleInput.component.js | 4 +- .../CellTitle/CellTitleInput.test.js | 2 +- .../CellTitle/CellTitleSelector.test.js | 2 +- .../HeaderCheckbox/HeaderCheckbox.test.js | 1 + .../HeaderResizable.component.js | 11 +- .../HeaderResizable.component.test.js | 4 +- .../VirtualizedList/event/rowclick.test.js | 1 + .../src/VirtualizedList/utils/gridrow.test.js | 13 +- packages/components/src/wrap.test.tsx | 1 + .../src/ActionButton/ActionButton.test.js | 7 +- .../ActionIconToggle/ActionIconToggle.test.js | 1 + .../src/ComponentForm/ComponentForm.test.js | 7 +- .../src/EditableText/EditableText.test.js | 8 +- packages/containers/src/Form/Form.test.js | 2 +- packages/containers/src/List/List.test.js | 9 +- .../ShortcutManager.container.js | 2 + .../ShortcutManager/ShortcutManager.test.js | 5 +- .../src/Typeahead/Typeahead.container.js | 6 +- .../src/Typeahead/Typeahead.test.js | 5 +- packages/design-system/package.json | 5 +- .../Enumeration/Enumeration.component.tsx | 173 +++++ .../Enumeration/Enumeration.module.scss | 25 + .../Enumeration/Enumeration.types.tsx | 22 + .../EnumerationHeader.component.tsx | 183 +++++ .../EnumerationHeader.module.scss | 28 + .../EnumerationIHeader.types.tsx | 16 + .../EnumerationItem.component.tsx | 104 +++ .../EnumerationItem.module.scss | 41 ++ .../EnumerationItem/EnumerationItem.types.tsx | 11 + .../src/components/Enumeration/index.ts | 1 + .../InlineEditing/InlineEditing.test.tsx | 5 +- .../Primitives/InlineEditingPrimitive.tsx | 22 +- packages/design-system/src/index.ts | 1 + .../stories/form/Enumeration/Enumeration.mdx | 52 ++ .../form/Enumeration/Enumeration.stories.tsx | 49 ++ .../stories/form/InlineEditing.stories.tsx | 12 + .../design-system/src/stories/status.json | 9 + .../AdvancedSearch.component.js | 6 +- .../AdvancedSearch.component.test.js | 2 +- .../BadgeCheckboxesForm.component.test.js | 2 +- .../BadgeMenu/BadgeMenuForm.component.test.js | 2 +- .../forms/src/UIForm/UIForm.container.test.js | 2 +- .../Datalist/Datalist.component.test.js | 2 +- .../displayMode/TextMode.component.test.js | 1 + .../UIForm/fields/Date/Date.component.test.js | 3 +- .../fields/Date/DateTime.component.test.js | 3 +- .../UIForm/fields/Date/Time.component.test.js | 3 +- .../fields/Enumeration/EnumerationWidget.js | 12 +- .../Enumeration/EnumerationWidget.test.js | 3 +- .../fields/ListView/ListView.component.js | 10 +- .../ListView/ListView.component.test.js | 2 +- .../MultiSelectTag.component.js | 11 +- .../NestedListView.component.js | 12 +- .../fields/Radios/Radios.component.test.js | 3 +- .../ResourcePicker.component.test.js | 3 +- .../fields/Select/Select.component.test.js | 2 +- .../UIForm/fields/Text/Text.component.test.js | 3 +- .../TextArea/TextArea.component.test.js | 3 +- .../fields/Toggle/Toggle.component.test.js | 3 +- .../fieldsets/Array/Array.component.test.js | 10 +- .../Array/ArrayItem.component.test.js | 2 +- .../forms/src/rhf/fields/Input/Input.test.js | 6 +- .../src/rhf/fields/Select/Select.test.js | 6 +- packages/jsfc/src/canonical-title-map.spec.js | 1 + packages/jsfc/src/merge.js | 4 +- packages/jsfc/src/merge.spec.js | 3 +- packages/jsfc/src/resolve.spec.js | 1 + packages/jsfc/src/schema-defaults.ts | 2 +- packages/jsfc/src/select.test.js | 2 +- packages/jsfc/src/sf-path.test.js | 2 +- packages/jsfc/src/traverse.test.js | 2 +- yarn.lock | 40 +- 195 files changed, 2003 insertions(+), 1107 deletions(-) create mode 100644 .changeset/orange-seahorses-talk.md create mode 100644 packages/design-system/src/components/Enumeration/Enumeration.component.tsx create mode 100644 packages/design-system/src/components/Enumeration/Enumeration.module.scss create mode 100644 packages/design-system/src/components/Enumeration/Enumeration.types.tsx create mode 100644 packages/design-system/src/components/Enumeration/EnumerationHeader/EnumerationHeader.component.tsx create mode 100644 packages/design-system/src/components/Enumeration/EnumerationHeader/EnumerationHeader.module.scss create mode 100644 packages/design-system/src/components/Enumeration/EnumerationHeader/EnumerationIHeader.types.tsx create mode 100644 packages/design-system/src/components/Enumeration/EnumerationItem/EnumerationItem.component.tsx create mode 100644 packages/design-system/src/components/Enumeration/EnumerationItem/EnumerationItem.module.scss create mode 100644 packages/design-system/src/components/Enumeration/EnumerationItem/EnumerationItem.types.tsx create mode 100644 packages/design-system/src/components/Enumeration/index.ts create mode 100644 packages/design-system/src/stories/form/Enumeration/Enumeration.mdx create mode 100644 packages/design-system/src/stories/form/Enumeration/Enumeration.stories.tsx diff --git a/.changeset/orange-seahorses-talk.md b/.changeset/orange-seahorses-talk.md new file mode 100644 index 00000000000..aaf34ae08bb --- /dev/null +++ b/.changeset/orange-seahorses-talk.md @@ -0,0 +1,5 @@ +--- +'@talend/design-system': minor +--- + +feat(TDC-7378): New enumeration component diff --git a/fork/react-bootstrap/src/Carousel.test.js b/fork/react-bootstrap/src/Carousel.test.js index 25564e7a529..a908f0de81d 100644 --- a/fork/react-bootstrap/src/Carousel.test.js +++ b/fork/react-bootstrap/src/Carousel.test.js @@ -1,317 +1,300 @@ import React from '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', 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); - }); + 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 c5fa0ca59ac..fe1fdedbc5a 100644 --- a/fork/react-bootstrap/src/CloseButton.test.js +++ b/fork/react-bootstrap/src/CloseButton.test.js @@ -1,77 +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', 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(); - }); + 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 18766e7f7b5..6dbde7b2a53 100644 --- a/fork/react-bootstrap/src/ControlLabel.test.js +++ b/fork/react-bootstrap/src/ControlLabel.test.js @@ -1,4 +1,5 @@ import React from 'react'; + import { render, screen } from '@testing-library/react'; import ControlLabel from './ControlLabel'; diff --git a/fork/react-bootstrap/src/Dropdown.js b/fork/react-bootstrap/src/Dropdown.js index 137ed555e31..2e892904e82 100644 --- a/fork/react-bootstrap/src/Dropdown.js +++ b/fork/react-bootstrap/src/Dropdown.js @@ -1,9 +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 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'; @@ -13,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'; @@ -22,332 +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.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; - } - })} - - ); - } + 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 ca3d832ec63..d592965d198 100644 --- a/fork/react-bootstrap/src/Dropdown.test.js +++ b/fork/react-bootstrap/src/Dropdown.test.js @@ -1,11 +1,12 @@ /* 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 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'; diff --git a/fork/react-bootstrap/src/DropdownMenu.js b/fork/react-bootstrap/src/DropdownMenu.js index 347e1462a15..8a7d631a60c 100644 --- a/fork/react-bootstrap/src/DropdownMenu.js +++ b/fork/react-bootstrap/src/DropdownMenu.js @@ -1,145 +1,126 @@ -import classNames from 'classnames'; 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.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), - }), - )} -
-
- ); - } + 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 ef69a260936..4f0c012ef71 100644 --- a/fork/react-bootstrap/src/DropdownMenu.test.js +++ b/fork/react-bootstrap/src/DropdownMenu.test.js @@ -1,6 +1,6 @@ // 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'; diff --git a/fork/react-bootstrap/src/Nav.js b/fork/react-bootstrap/src/Nav.js index 350a818a084..b98dd600c18 100644 --- a/fork/react-bootstrap/src/Nav.js +++ b/fork/react-bootstrap/src/Nav.js @@ -1,7 +1,8 @@ -import classNames from 'classnames'; 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'; diff --git a/fork/react-bootstrap/test/DropdownMenuSpec.js b/fork/react-bootstrap/test/DropdownMenuSpec.js index aa3dc04d61b..55739c8f879 100644 --- a/fork/react-bootstrap/test/DropdownMenuSpec.js +++ b/fork/react-bootstrap/test/DropdownMenuSpec.js @@ -1,10 +1,10 @@ import ReactDOM from 'react-dom'; import ReactTestUtils from 'react-dom/test-utils'; + import { shallow } from 'enzyme'; import DropdownMenu from '../src/DropdownMenu'; import MenuItem from '../src/MenuItem'; - import { getOne } from './helpers'; describe('', () => { diff --git a/fork/react-bootstrap/test/DropdownSpec.js b/fork/react-bootstrap/test/DropdownSpec.js index 79fcba18dda..9b446215a34 100644 --- a/fork/react-bootstrap/test/DropdownSpec.js +++ b/fork/react-bootstrap/test/DropdownSpec.js @@ -1,13 +1,13 @@ import React from 'react'; import ReactDOM from 'react-dom'; import ReactTestUtils from 'react-dom/test-utils'; + import { mount, shallow } from 'enzyme'; import Dropdown from '../src/Dropdown'; import DropdownMenu from '../src/DropdownMenu'; import Grid from '../src/Grid'; import MenuItem from '../src/MenuItem'; - import { shouldWarn } from './helpers'; class CustomMenu extends React.Component { diff --git a/fork/react-bootstrap/test/MenuItemSpec.js b/fork/react-bootstrap/test/MenuItemSpec.js index 5be3275eb17..3c46b194e92 100644 --- a/fork/react-bootstrap/test/MenuItemSpec.js +++ b/fork/react-bootstrap/test/MenuItemSpec.js @@ -1,9 +1,9 @@ import ReactDOM from 'react-dom'; import ReactTestUtils from 'react-dom/test-utils'; + import { shallow } from 'enzyme'; import MenuItem from '../src/MenuItem'; - import { shouldWarn } from './helpers'; describe('', () => { diff --git a/fork/react-bootstrap/test/NavSpec.js b/fork/react-bootstrap/test/NavSpec.js index d55adf71fbe..992ccf6f0b5 100644 --- a/fork/react-bootstrap/test/NavSpec.js +++ b/fork/react-bootstrap/test/NavSpec.js @@ -1,9 +1,9 @@ import ReactTestUtils from 'react-dom/test-utils'; + import { mount } from 'enzyme'; import Nav from '../src/Nav'; import NavItem from '../src/NavItem'; - import { shouldWarn } from './helpers'; describe('