From 24bcb177f934a0694d95cca0d13748c209abe398 Mon Sep 17 00:00:00 2001 From: Jean-Michel FRANCOIS Date: Wed, 29 Nov 2023 09:12:03 +0100 Subject: [PATCH] chore: upgrade testing library (#4954) --- .changeset/heavy-geckos-grab.md | 11 + .changeset/strong-keys-drum.md | 5 + fork/react-bootstrap/package.json | 10 +- fork/react-bootstrap/src/Alert.test.js | 173 +- .../src/BreadcrumbItem.test.js | 265 ++-- fork/react-bootstrap/src/Button.test.js | 225 +-- fork/react-bootstrap/src/Carousel.test.js | 96 +- fork/react-bootstrap/src/CloseButton.test.js | 6 +- fork/react-bootstrap/src/ControlLabel.test.js | 100 +- fork/react-bootstrap/src/Dropdown.js | 25 +- fork/react-bootstrap/src/Dropdown.test.js | 1408 ++++++++--------- fork/react-bootstrap/src/DropdownMenu.js | 30 +- fork/react-bootstrap/src/DropdownMenu.test.js | 457 +++--- fork/react-bootstrap/src/Nav.js | 636 ++++---- fork/react-bootstrap/src/Nav.test.js | 598 +++---- fork/react-bootstrap/src/helpers.js | 19 +- fork/react-bootstrap/test/DropdownMenuSpec.js | 438 +++-- fork/react-bootstrap/test/DropdownSpec.js | 1237 +++++++-------- fork/react-bootstrap/test/MenuItemSpec.js | 447 +++--- fork/react-bootstrap/test/NavSpec.js | 572 ++++--- fork/react-bootstrap/test/helpers.js | 19 +- packages/a11y/package.json | 9 +- .../a11y/src/Gesture/preventScroll.test.tsx | 102 ++ packages/a11y/src/Gesture/preventScroll.ts | 22 +- .../src/Gesture/withCalendarGesture.test.tsx | 107 +- .../a11y/src/Gesture/withCalendarGesture.tsx | 23 +- .../Gesture/withDynamicListGesture.test.js | 20 +- .../src/Gesture/withDynamicListGesture.tsx | 13 +- .../a11y/src/Gesture/withListGesture.test.js | 16 +- packages/a11y/src/Gesture/withListGesture.tsx | 9 +- .../Gesture/withMonthCalendarGesture.test.tsx | 52 +- .../src/Gesture/withMonthCalendarGesture.tsx | 19 +- .../a11y/src/Gesture/withTreeGesture.test.js | 60 +- packages/a11y/src/Gesture/withTreeGesture.tsx | 26 +- packages/a11y/tsconfig.json | 4 +- packages/cmf-cqrs/package.json | 2 +- .../src/middleware/smartWebsocket.test.js | 4 +- packages/cmf/__tests__/App.test.js | 6 +- packages/cmf/__tests__/bootstrap.test.js | 2 +- packages/cmf/__tests__/cmfConnect.test.js | 6 +- packages/cmf/__tests__/expression.test.js | 2 +- .../reducers/componentsReducers.test.js | 4 +- packages/cmf/__tests__/registry.test.js | 4 +- packages/cmf/package.json | 2 +- packages/components/package.json | 7 +- .../ActionIntercom/Intercom.component.test.js | 16 +- .../ActionIntercom/Intercom.service.test.js | 14 +- .../src/ActionList/ActionList.test.js | 6 +- .../Actions/ActionButton/ActionButton.test.js | 50 +- .../ActionDropdown/ActionDropdown.test.js | 35 +- .../ActionIconToggle.component.test.js | 10 +- .../ActionSplitDropdown.test.js | 8 +- .../src/AppGuidedTour/AppGuidedTour.test.js | 26 +- .../AppSwitcher/AppSwitcher.component.test.js | 13 +- .../BadgeDelete/BadgeDelete.component.test.js | 5 +- .../BadgeDropdown.component.test.js | 6 +- .../src/Breadcrumbs/Breadcrumbs.test.js | 12 +- .../CollapsiblePanel/CollapsiblePanel.test.js | 16 +- .../src/ConfirmDialog/ConfirmDialog.test.js | 6 +- .../Headers/TreeHeader/TreeHeader.test.js | 8 +- .../Managers/TreeManager/TreeManager.test.js | 8 +- .../Branch/RecordsViewerBranch.component.js | 8 +- .../Branch/RecordsViewerBranch.test.js | 10 +- .../CellRenderer/RecordsCellRenderer.test.js | 6 +- .../src/Datalist/Datalist.component.js | 16 +- .../src/Datalist/Datalist.component.test.js | 263 +-- .../Date/Manager/Manager.component.test.js | 45 +- .../Manager/Manager.component.test.js | 43 +- .../DateRange/Picker/Picker.component.test.js | 4 +- .../Manager/Manager.component.test.js | 72 +- .../Manager/Manager.component.test.js | 16 +- .../InputDateTimePicker.component.test.js | 16 +- .../InputDateTimeRangePicker.component.js | 2 +- ...InputDateTimeRangePicker.component.test.js | 20 +- .../InputTimePicker.component.test.js | 60 +- .../DateTime/Input/Input.component.test.js | 10 +- .../Manager/Manager.component.test.js | 122 +- .../InputDateTimePicker.component.js | 9 +- .../InputDateTimePicker.component.test.js | 69 +- .../pickers/DatePicker/DatePicker.test.js | 14 +- .../DateTimePicker/DateTimePicker.test.js | 22 +- .../pickers/MonthPicker/MonthPicker.test.js | 8 +- .../pickers/TimePicker/TimePicker.test.js | 44 +- .../pickers/YearPicker/YearPicker.test.js | 22 +- .../views/DateTimeView/DateTimeView.test.js | 34 +- .../views/MonthYearView/MonthYearView.test.js | 10 +- .../Time/Manager/Manager.component.test.js | 50 +- .../hooks/useInputPickerHandlers.js | 9 +- .../CalendarPicker/CalendarPicker.test.js | 66 +- .../pickers/DatePicker/DatePicker.test.js | 22 +- .../pickers/MonthPicker/MonthPicker.test.js | 8 +- .../TimePicker/TimePicker.component.test.js | 10 +- .../pickers/YearPicker/YearPicker.test.js | 22 +- .../views/DateView/DateView.test.js | 22 +- .../views/MonthYearView/MonthYearView.test.js | 11 +- packages/components/src/Dialog/Dialog.test.js | 6 +- .../src/EditableText/InlineForm.component.js | 6 +- .../EditableText/InlineForm.component.test.js | 41 +- .../PlainTextTitle.component.test.js | 6 +- .../src/Enumeration/Header/Header.test.js | 8 +- .../Enumeration/Header/HeaderInput.test.js | 12 +- .../Enumeration/Header/headerSelected.test.js | 8 +- .../src/Enumeration/Items/Item/Item.test.js | 16 +- .../Items/Item/ItemEdit.component.js | 9 +- .../Enumeration/Items/Item/ItemEdit.test.js | 30 +- .../src/FilterBar/FilterBar.component.js | 9 +- .../src/FilterBar/FilterBar.test.js | 122 +- .../GridLayout/Tile/Tile.component.test.tsx | 9 +- .../src/HeaderBar/HeaderBar.test.js | 50 +- .../ColumnChooser.component.test.js | 26 +- .../ListDisplayMode.component.test.js | 35 +- .../Manager/ListManager.component.test.js | 32 +- .../hooks/useCollectionSelection.hook.test.js | 26 +- .../hooks/useCollectionSort.hook.test.js | 6 +- .../SortBy/SortBy.component.test.js | 16 +- .../TextFilter/TextFilter.component.test.js | 38 +- .../ListToVirtualizedList.test.js | 58 +- .../ColumnChooser.component.test.js | 6 +- .../ColumnChooserBody.component.test.js | 4 +- .../RowCheckbox/RowCheckbox.component.test.js | 5 +- ...SelectAllColumnsCheckbox.component.test.js | 12 +- .../DisplayModeToggle.test.js | 6 +- .../Toolbar/Pagination/Pagination.test.js | 28 +- .../Toolbar/SelectSortBy/SelectSortBy.test.js | 8 +- .../src/ListView/Header/Header.test.js | 8 +- .../src/ListView/Header/headerInput.test.js | 8 +- .../src/MultiSelect/MultiSelect.container.js | 3 +- .../src/Notification/Notification.test.js | 32 +- .../ObjectViewer/JSONLike/JSONLike.test.js | 24 +- .../src/OverlayTrigger/OverlayTrigger.test.js | 12 +- .../src/PieChart/PieChartButton.test.js | 10 +- .../src/RadarChart/RadarChart.test.js | 12 +- .../src/RatioBar/RatioBar.component.test.js | 6 +- .../RatioBar/RatioBarComposition.component.js | 8 +- .../NameFilter/NameFilter.snap.test.js | 6 +- .../Toolbar/NameFilter/NameFilter.test.js | 17 +- .../OrderChooser/OrderChooser.test.js | 6 +- .../Toolbar/SortOptions/SortOptions.test.js | 14 +- .../Toolbar/StateFilter/StateFilter.test.js | 14 +- .../src/SidePanel/SidePanel.test.js | 14 +- .../components/src/TabBar/TabBar.component.js | 7 +- packages/components/src/TabBar/TabBar.test.js | 11 +- .../LabelToggle/LabelToggleComponent.test.js | 5 +- packages/components/src/Toggle/Toggle.test.js | 6 +- .../src/TooltipTrigger/TooltipTrigger.test.js | 26 +- .../components/src/TreeView/TreeView.test.js | 10 +- .../TreeViewItem/TreeViewItem.test.js | 42 +- .../src/Typeahead/Typeahead.component.js | 14 +- .../src/Typeahead/Typeahead.test.js | 36 +- .../CellCheckbox/CellCheckbox.test.js | 8 +- .../CellTitle/CellTitleActions.test.js | 19 +- .../CellTitle/CellTitleInput.component.js | 4 +- .../CellTitle/CellTitleInput.test.js | 20 +- .../CellTitle/CellTitleSelector.test.js | 8 +- .../HeaderCheckbox/HeaderCheckbox.test.js | 6 +- .../HeaderResizable.component.js | 13 +- .../HeaderResizable.component.test.js | 6 +- .../VirtualizedList/event/rowclick.test.js | 6 +- .../src/VirtualizedList/utils/gridrow.test.js | 2 +- packages/components/src/wrap.test.tsx | 7 +- packages/components/tsconfig.json | 1 + packages/containers/package.json | 5 +- .../src/ActionButton/ActionButton.test.js | 2 +- .../ActionIconToggle/ActionIconToggle.test.js | 2 +- .../src/ComponentForm/ComponentForm.test.js | 8 +- .../src/EditableText/EditableText.test.js | 18 +- packages/containers/src/Form/Form.test.js | 4 +- packages/containers/src/List/List.test.js | 16 +- .../ShortcutManager.container.js | 6 +- .../ShortcutManager/ShortcutManager.test.js | 23 +- .../src/Typeahead/Typeahead.container.js | 14 +- .../src/Typeahead/Typeahead.test.js | 5 +- packages/dataviz/cypress/support/commands.ts | 1 - packages/dataviz/package.json | 6 +- packages/design-system/.prettierrc.js | 1 + packages/design-system/package.json | 5 +- .../InlineEditing/InlineEditing.test.tsx | 6 +- .../Primitives/InlineEditingPrimitive.tsx | 5 +- .../components/Tabs/Primitive/TabPanel.tsx | 2 + .../src/components/Tabs/Primitive/Tabs.tsx | 12 +- .../src/components/Tabs/variants/Tabs.tsx | 8 +- .../src/stories/navigation/Tabs.stories.tsx | 1 + packages/faceted-search/package.json | 5 +- .../AdvancedSearch.component.js | 5 +- .../AdvancedSearch.component.test.js | 5 +- .../BadgeCheckboxesForm.component.test.js | 16 +- .../BadgeMenu/BadgeMenuForm.component.test.js | 20 +- packages/flow-designer/package.json | 4 +- packages/flow-designer/tsconfig.json | 4 +- packages/forms/package.json | 5 +- .../forms/src/UIForm/UIForm.container.test.js | 24 +- .../UIForm/fields/CheckBox/CheckBox.test.js | 10 +- .../UIForm/fields/CheckBox/CheckBoxes.test.js | 13 +- .../fields/CheckBox/SimpleCheckBox.test.js | 7 +- .../Datalist/Datalist.component.test.js | 2 +- .../displayMode/TextMode.component.test.js | 2 +- .../UIForm/fields/Date/Date.component.test.js | 26 +- .../fields/Date/DateTime.component.test.js | 12 +- .../UIForm/fields/Date/Time.component.test.js | 10 +- .../fields/Enumeration/EnumerationWidget.js | 6 +- .../Enumeration/EnumerationWidget.test.js | 16 +- .../fields/ListView/ListView.component.js | 6 +- .../ListView/ListView.component.test.js | 32 +- .../MultiSelectTag.component.js | 14 +- .../MultiSelectTag.component.test.js | 55 +- .../NestedListView.component.js | 6 +- .../fields/Radios/Radios.component.test.js | 7 +- .../ResourcePicker.component.test.js | 129 +- .../fields/Select/Select.component.test.js | 2 +- .../UIForm/fields/Text/Text.component.test.js | 2 +- .../TextArea/TextArea.component.test.js | 4 +- .../fields/Toggle/Toggle.component.test.js | 4 +- .../UIForm/fieldsets/Array/Array.component.js | 12 +- .../fieldsets/Array/Array.component.test.js | 16 +- .../Array/ArrayItem.component.test.js | 6 +- .../CollapsibleFieldset.component.js | 8 +- .../CollapsibleFieldset.component.test.js | 11 +- .../UIForm/fieldsets/Tabs/Tabs.component.js | 3 +- .../forms/src/rhf/fields/Input/Input.test.js | 62 +- .../src/rhf/fields/Select/Select.test.js | 95 +- .../forms/stories/SchemaFieldsets.stories.tsx | 3 +- .../forms/stories/SchemaState.stories.tsx | 18 +- packages/icons/package.json | 148 +- packages/stepper/package.json | 1 - packages/storybook-cmf/package.json | 2 +- packages/storybook-docs/.prettierrc.js | 1 + tools/scripts-config-cdn/umds.json | 9 - tools/scripts-config-eslint/.eslintrc.json | 7 + tools/scripts-config-jest/package.json | 2 +- tools/scripts-config-jest/test-setup.js | 2 +- tools/scripts-yarn-workspace/package.json | 48 +- versions/dependencies.json | 1 - yarn.lock | 76 +- 233 files changed, 5728 insertions(+), 5197 deletions(-) create mode 100644 .changeset/heavy-geckos-grab.md create mode 100644 .changeset/strong-keys-drum.md create mode 100644 packages/a11y/src/Gesture/preventScroll.test.tsx diff --git a/.changeset/heavy-geckos-grab.md b/.changeset/heavy-geckos-grab.md new file mode 100644 index 00000000000..48c1e68406c --- /dev/null +++ b/.changeset/heavy-geckos-grab.md @@ -0,0 +1,11 @@ +--- +'@talend/react-faceted-search': minor +'@talend/design-system': minor +'@talend/react-bootstrap': minor +'@talend/react-components': minor +'@talend/react-containers': minor +'@talend/react-forms': minor +'@talend/react-a11y': minor +--- + +Remove usage of lib keyCode diff --git a/.changeset/strong-keys-drum.md b/.changeset/strong-keys-drum.md new file mode 100644 index 00000000000..1c58b1bee7e --- /dev/null +++ b/.changeset/strong-keys-drum.md @@ -0,0 +1,5 @@ +--- +'@talend/scripts-config-jest': major +--- + +chore: bump testing-library to 6.x diff --git a/fork/react-bootstrap/package.json b/fork/react-bootstrap/package.json index 81442fade0d..c7d48702996 100644 --- a/fork/react-bootstrap/package.json +++ b/fork/react-bootstrap/package.json @@ -19,9 +19,6 @@ "storybook": "start-storybook -p 6006", "build-storybook": "build-storybook" }, - "prettier": { - "singleQuote": true - }, "files": [ "CHANGELOG.md", "lib", @@ -46,9 +43,9 @@ "@talend/scripts-core": "^16.3.0", "@talend/scripts-config-babel": "^13.2.0", "@talend/scripts-config-react-webpack": "^16.3.1", - "@testing-library/jest-dom": "^5.17.0", - "@testing-library/react": "^12.1.5", - "@testing-library/user-event": "^13.5.0", + "@testing-library/jest-dom": "^6.1.4", + "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.5.1", "chai": "^4.3.10", "chalk": "^2.4.2", "create-react-class": "^15.7.0", @@ -63,7 +60,6 @@ "classnames": "^2.3.2", "dom-helpers": "^3.4.0", "invariant": "^2.2.4", - "keycode": "^2.2.1", "prop-types": "^15.8.1", "prop-types-extra": "^1.1.1", "react-overlays": "^0.9.3", diff --git a/fork/react-bootstrap/src/Alert.test.js b/fork/react-bootstrap/src/Alert.test.js index 30acd97cc4a..33b96a18fa6 100644 --- a/fork/react-bootstrap/src/Alert.test.js +++ b/fork/react-bootstrap/src/Alert.test.js @@ -1,92 +1,95 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import Alert from './Alert'; describe('', () => { - it('Should output a alert with message', () => { - // when - render( - - Message - - ); - - // then - expect(screen.getByRole('alert')).toBeInTheDocument(); - expect(screen.getByText('Message')).toBeInTheDocument(); - }); - - it('Should have bsType by default', () => { - // when - render( - - Message - - ); - - // then - expect(screen.getByRole('alert')).toHaveClass('alert-info'); - }); - - it('Should have dismissable style with onDismiss', () => { - // when - render( - - Message - - ); - - // then - expect(screen.getByRole('alert')).toHaveClass('alert-dismissable'); - }); - - it('Should call onDismiss callback on dismiss click', () => { - // given - const onDismiss = jest.fn(); - render( - - Message - - ); - expect(onDismiss).not.toBeCalled(); - - // when - userEvent.click(screen.getByRole('button', { name: 'close' })); - - // then - expect(onDismiss).toBeCalled(); - }); - - it('Should have a default bsStyle class', () => { - // when - render(Message); - - // then - expect(screen.getByRole('alert')).toHaveClass('alert-info'); - }); - - it('Should have use bsStyle class', () => { - // when - render(Message); - - // then - expect(screen.getByRole('alert')).toHaveClass('alert-danger'); - }); - - describe('Web Accessibility', () => { - it('Should call onDismiss callback when the sr-only dismiss link is activated', () => { - // given - const onDismiss = jest.fn(); - render(Message); - expect(onDismiss).not.toBeCalled(); - - // when - userEvent.click(screen.getByRole('button', { name: 'Close alert' })); - - // then - expect(onDismiss).toBeCalled(); - }); - }); + it('Should output a alert with message', () => { + // when + render( + + Message + , + ); + + // then + expect(screen.getByRole('alert')).toBeInTheDocument(); + expect(screen.getByText('Message')).toBeInTheDocument(); + }); + + it('Should have bsType by default', () => { + // when + render( + + Message + , + ); + + // then + expect(screen.getByRole('alert')).toHaveClass('alert-info'); + }); + + it('Should have dismissable style with onDismiss', () => { + // when + render( + + Message + , + ); + + // then + expect(screen.getByRole('alert')).toHaveClass('alert-dismissable'); + }); + + it('Should call onDismiss callback on dismiss click', async () => { + const user = userEvent.setup(); + + // given + const onDismiss = jest.fn(); + render( + + Message + , + ); + expect(onDismiss).not.toHaveBeenCalled(); + + // when + await user.click(screen.getByRole('button', { name: 'close' })); + + // then + expect(onDismiss).toHaveBeenCalled(); + }); + + it('Should have a default bsStyle class', () => { + // when + render(Message); + + // then + expect(screen.getByRole('alert')).toHaveClass('alert-info'); + }); + + it('Should have use bsStyle class', () => { + // when + render(Message); + + // then + expect(screen.getByRole('alert')).toHaveClass('alert-danger'); + }); + + describe('Web Accessibility', () => { + it('Should call onDismiss callback when the sr-only dismiss link is activated', async () => { + const user = userEvent.setup(); + + // given + const onDismiss = jest.fn(); + render(Message); + expect(onDismiss).not.toHaveBeenCalled(); + + // when + await user.click(screen.getByRole('button', { name: 'Close alert' })); + + // then + expect(onDismiss).toHaveBeenCalled(); + }); + }); }); diff --git a/fork/react-bootstrap/src/BreadcrumbItem.test.js b/fork/react-bootstrap/src/BreadcrumbItem.test.js index dd668ba9cc9..9766108e9a0 100644 --- a/fork/react-bootstrap/src/BreadcrumbItem.test.js +++ b/fork/react-bootstrap/src/BreadcrumbItem.test.js @@ -1,144 +1,135 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import Breadcrumb from './Breadcrumb'; describe('', () => { - it('Should render `a` as inner element when is not active', () => { - // when - render(Crumb); - - // then - const link = screen.getByRole('button'); - expect(link).toBeInTheDocument(); - expect(link.tagName).toBe('A'); - expect(link).not.toHaveClass('active'); - }); - - it('Should render `span.active` with `active` attribute set.', () => { - // when - render(Active Crumb); - - // then - const item = screen.getByRole('listitem'); - expect(item).toBeInTheDocument(); - expect(item).toHaveClass('active'); - }); - - it('Should render `span.active` when active and has href', () => { - // when - render( - - Active Crumb - - ); - - // then - const item = screen.getByRole('listitem'); - expect(item).toBeInTheDocument(); - expect(item).toHaveClass('active'); - expect(screen.queryByRole('button')).not.toBeInTheDocument(); - }); - - it('Should add custom classes onto `li` wrapper element', () => { - // when - render( - - Active Crumb - - ); - - // then - const item = screen.getByRole('listitem'); - expect(item).toHaveClass('custom-one'); - expect(item).toHaveClass('custom-two'); - }); - - it('Should spread additional props onto inner element', () => { - // given - const handleClick = jest.fn(); - render( - - Crumb - - ); - - // when - userEvent.click(screen.getByRole('button')); - - // then - expect(handleClick).toHaveBeenCalled(); - }); - - it('Should apply id onto the anchor', () => { - // when - render( - - Crumb - - ); - - // then - expect(screen.getByRole('button')).toHaveAttribute('id', 'test-link-id'); - }); - - it('Should apply `href` property onto `a` inner element', () => { - // when - render( - - Crumb - - ); - - // then - expect(screen.getByRole('link')).toHaveAttribute( - 'href', - 'http://getbootstrap.com/components/#breadcrumbs' - ); - }); - - it('Should apply `title` property onto `a` inner element', () => { - // when - render( - - Crumb - - ); - - // then - expect(screen.getByRole('link')).toHaveAttribute('title', 'test-title'); - }); - - it('Should not apply properties for inner `anchor` onto `li` wrapper element', () => { - // when - render( - - Crumb - - ); - - // then - const listitem = screen.getByRole('listitem'); - expect(listitem).not.toHaveAttribute('title'); - expect(listitem).not.toHaveAttribute('href'); - }); - - it('Should set `target` attribute on `anchor`', () => { - // when - render( - - Crumb - - ); - - // then - expect(screen.getByRole('link')).toHaveAttribute('target', '_blank'); - }); + it('Should render `a` as inner element when is not active', () => { + // when + render(Crumb); + + // then + const link = screen.getByRole('button'); + expect(link).toBeInTheDocument(); + expect(link.tagName).toBe('A'); + expect(link).not.toHaveClass('active'); + }); + + it('Should render `span.active` with `active` attribute set.', () => { + // when + render(Active Crumb); + + // then + const item = screen.getByRole('listitem'); + expect(item).toBeInTheDocument(); + expect(item).toHaveClass('active'); + }); + + it('Should render `span.active` when active and has href', () => { + // when + render( + + Active Crumb + , + ); + + // then + const item = screen.getByRole('listitem'); + expect(item).toBeInTheDocument(); + expect(item).toHaveClass('active'); + expect(screen.queryByRole('button')).not.toBeInTheDocument(); + }); + + it('Should add custom classes onto `li` wrapper element', () => { + // when + render(Active Crumb); + + // then + const item = screen.getByRole('listitem'); + expect(item).toHaveClass('custom-one'); + expect(item).toHaveClass('custom-two'); + }); + + it('Should spread additional props onto inner element', async () => { + const user = userEvent.setup(); + + // given + const handleClick = jest.fn(); + render( + + Crumb + , + ); + + // when + await user.click(screen.getByRole('button')); + + // then + expect(handleClick).toHaveBeenCalled(); + }); + + it('Should apply id onto the anchor', () => { + // when + render( + + Crumb + , + ); + + // then + expect(screen.getByRole('button')).toHaveAttribute('id', 'test-link-id'); + }); + + it('Should apply `href` property onto `a` inner element', () => { + // when + render( + + Crumb + , + ); + + // then + expect(screen.getByRole('link')).toHaveAttribute( + 'href', + 'http://getbootstrap.com/components/#breadcrumbs', + ); + }); + + it('Should apply `title` property onto `a` inner element', () => { + // when + render( + + Crumb + , + ); + + // then + expect(screen.getByRole('link')).toHaveAttribute('title', 'test-title'); + }); + + it('Should not apply properties for inner `anchor` onto `li` wrapper element', () => { + // when + render( + + Crumb + , + ); + + // then + const listitem = screen.getByRole('listitem'); + expect(listitem).not.toHaveAttribute('title'); + expect(listitem).not.toHaveAttribute('href'); + }); + + it('Should set `target` attribute on `anchor`', () => { + // when + render( + + Crumb + , + ); + + // then + expect(screen.getByRole('link')).toHaveAttribute('target', '_blank'); + }); }); diff --git a/fork/react-bootstrap/src/Button.test.js b/fork/react-bootstrap/src/Button.test.js index b5d67347451..df9107f17da 100644 --- a/fork/react-bootstrap/src/Button.test.js +++ b/fork/react-bootstrap/src/Button.test.js @@ -1,119 +1,120 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import Button from './Button'; describe('); - - // 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 02252274973..25564e7a529 100644 --- a/fork/react-bootstrap/src/Carousel.test.js +++ b/fork/react-bootstrap/src/Carousel.test.js @@ -1,5 +1,5 @@ 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'; @@ -48,7 +48,7 @@ describe('', () => { {null} {false} Item 2 content - + , ); // then @@ -64,41 +64,47 @@ describe('', () => { expect(list.querySelectorAll('li')).toHaveLength(2); }); - it('Should call onSelect when indicator selected', () => { + it('Should call onSelect when indicator selected', async () => { + const user = userEvent.setup(); + // given const onSelect = jest.fn(); render( {items} - + , ); // when - userEvent.click(screen.getAllByRole('listitem')[0]); + await user.click(screen.getAllByRole('listitem')[0]); // then - expect(onSelect).toBeCalledWith(0); + expect(onSelect).toHaveBeenCalledWith(0); }); - it('Should call onSelect with direction', () => { + 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 - userEvent.click(screen.getAllByRole('listitem')[0]); + await user.click(screen.getAllByRole('listitem')[0]); // then - expect(onSelect).toBeCalled(); + 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', () => { + 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'); @@ -112,54 +118,56 @@ describe('', () => { render( {items} - + , ); // when - userEvent.click(screen.getByRole('button', { name: 'Next' })); + await user.click(screen.getByRole('button', { name: 'Next' })); // then - expect(onSelect).toBeCalled(); + 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', () => { + it('Should show back button control on the first image if wrap is true', async () => { + const user = userEvent.setup(); + // given - jest.useFakeTimers(); render( {items} - + , ); expect(screen.getByText('Item 1 content')).toHaveClass('active'); // when - userEvent.click(screen.getByRole('button', { name: 'Previous' })); - jest.runAllTimers(); + await user.click(screen.getByRole('button', { name: 'Previous' })); // then - expect(screen.getByText('Item 2 content')).toHaveClass('active'); - jest.useRealTimers(); + await waitFor(() => + expect(screen.getByText('Item 2 content')).toHaveClass('active'), + ); }); - it('Should show next button control on the last image if wrap is true', () => { + it('Should show next button control on the last image if wrap is true', async () => { + const user = userEvent.setup(); + // given - jest.useFakeTimers(); render( {items} - + , ); expect(screen.getByText('Item 2 content')).toHaveClass('active'); // when - userEvent.click(screen.getByRole('button', { name: 'Next' })); - jest.runAllTimers(); + await user.click(screen.getByRole('button', { name: 'Next' })); // then - expect(screen.getByText('Item 1 content')).toHaveClass('active'); - jest.useRealTimers(); + 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', () => { @@ -167,12 +175,12 @@ describe('', () => { render( {items} - + , ); // then expect( - screen.queryByRole('button', { name: 'Previous' }) + screen.queryByRole('button', { name: 'Previous' }), ).not.toBeInTheDocument(); }); @@ -181,12 +189,12 @@ describe('', () => { render( {items} - + , ); // then expect( - screen.queryByRole('button', { name: 'Next' }) + screen.queryByRole('button', { name: 'Next' }), ).not.toBeInTheDocument(); }); @@ -203,15 +211,15 @@ describe('', () => { Item 1 content Item 2 content Item 3 content - + , ); // then expect( - screen.getByRole('button', { name: 'Previous' }).firstChild + screen.getByRole('button', { name: 'Previous' }).firstChild, ).toHaveClass('ficon-left'); expect(screen.getByRole('button', { name: 'Next' }).firstChild).toHaveClass( - 'ficon-right' + 'ficon-right', ); }); @@ -228,35 +236,37 @@ describe('', () => { Item 1 content Item 2 content Item 3 content - + , ); // then expect( - screen.getByRole('button', { name: 'Previous awesomeness' }) + screen.getByRole('button', { name: 'Previous awesomeness' }), ).toBeInTheDocument(); expect( - screen.getByRole('button', { name: 'Next awesomeness' }) + screen.getByRole('button', { name: 'Next awesomeness' }), ).toBeInTheDocument(); }); - it('Should transition properly when slide animation is disabled', () => { + 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 - userEvent.click(screen.getByRole('button', { name: 'Next' })); + await user.click(screen.getByRole('button', { name: 'Next' })); // then expect(screen.getByText('Item 2 content')).toHaveClass('active'); // when - userEvent.click(screen.getByRole('button', { name: 'Previous' })); + await user.click(screen.getByRole('button', { name: 'Previous' })); // then expect(screen.getByText('Item 1 content')).toHaveClass('active'); @@ -267,7 +277,7 @@ describe('', () => { // default active is the 2nd item, which will be removed on // subsequent render const { rerender } = render( - {items} + {items}, ); expect(screen.getByText('Item 1 content')).not.toHaveClass('active'); diff --git a/fork/react-bootstrap/src/CloseButton.test.js b/fork/react-bootstrap/src/CloseButton.test.js index a6dd026600a..c5fa0ca59ac 100644 --- a/fork/react-bootstrap/src/CloseButton.test.js +++ b/fork/react-bootstrap/src/CloseButton.test.js @@ -32,14 +32,16 @@ describe('', () => { expect(screen.getByRole('button')).toHaveClass('close'); }); - it('Should call onClick callback', () => { + it('Should call onClick callback', async () => { + const user = userEvent.setup(); + // given const onClick = jest.fn(); render(); expect(onClick).not.toHaveBeenCalled(); // when - userEvent.click(screen.getByRole('button')); + await user.click(screen.getByRole('button')); // then expect(onClick).toHaveBeenCalled(); diff --git a/fork/react-bootstrap/src/ControlLabel.test.js b/fork/react-bootstrap/src/ControlLabel.test.js index 3c7589d4d49..18766e7f7b5 100644 --- a/fork/react-bootstrap/src/ControlLabel.test.js +++ b/fork/react-bootstrap/src/ControlLabel.test.js @@ -5,64 +5,64 @@ 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 a992679e589..137ed555e31 100644 --- a/fork/react-bootstrap/src/Dropdown.js +++ b/fork/react-bootstrap/src/Dropdown.js @@ -1,7 +1,6 @@ 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'; @@ -34,7 +33,7 @@ const propTypes = { * @required */ id: isRequiredForA11y( - PropTypes.oneOfType([PropTypes.string, PropTypes.number]) + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), ), componentClass: elementType, @@ -45,7 +44,7 @@ const propTypes = { */ children: all( requiredRoles(TOGGLE_ROLE, MENU_ROLE), - exclusiveRoles(MENU_ROLE) + exclusiveRoles(MENU_ROLE), ), /** @@ -143,7 +142,7 @@ class Dropdown extends React.Component { if (!open && prevOpen) { this._focusInDropdown = contains( ReactDOM.findDOMNode(this.menu), - activeElement(document) + activeElement(document), ); // if focus hasn't already moved from the menu let's return it // to the toggle @@ -198,8 +197,9 @@ class Dropdown extends React.Component { return; } - switch (event.keyCode) { - case keycode.codes.down: + switch (event.key) { + case 'Down': + case 'ArrowDown': if (!this.props.open) { this.toggleOpen(event, { source: 'keydown' }); } else if (this.menu.focusNext) { @@ -207,8 +207,9 @@ class Dropdown extends React.Component { } event.preventDefault(); break; - case keycode.codes.esc: - case keycode.codes.tab: + case 'Esc': + case 'Escape': + case 'Tab': this.handleClose(event, { source: 'keydown' }); break; default: @@ -237,7 +238,7 @@ class Dropdown extends React.Component { 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' + 'https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute', ); } else { ref = createChainedFunction(child.ref, ref); @@ -252,7 +253,7 @@ class Dropdown extends React.Component { onSelect: createChainedFunction( child.props.onSelect, onSelect, - (key, event) => this.handleClose(event, { source: 'select' }) + (key, event) => this.handleClose(event, { source: 'select' }), ), rootCloseEvent, }); @@ -268,7 +269,7 @@ class Dropdown extends React.Component { 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' + 'https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute', ); } else { ref = createChainedFunction(child.ref, ref); @@ -281,7 +282,7 @@ class Dropdown extends React.Component { onClick: createChainedFunction(child.props.onClick, this.handleClick), onKeyDown: createChainedFunction( child.props.onKeyDown, - this.handleKeyDown + this.handleKeyDown, ), }); } diff --git a/fork/react-bootstrap/src/Dropdown.test.js b/fork/react-bootstrap/src/Dropdown.test.js index f4f804b3b72..ca3d832ec63 100644 --- a/fork/react-bootstrap/src/Dropdown.test.js +++ b/fork/react-bootstrap/src/Dropdown.test.js @@ -4,7 +4,6 @@ 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 Dropdown from './Dropdown'; @@ -12,721 +11,700 @@ 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 249346926e3..347e1462a15 100644 --- a/fork/react-bootstrap/src/DropdownMenu.js +++ b/fork/react-bootstrap/src/DropdownMenu.js @@ -1,5 +1,4 @@ import classNames from 'classnames'; -import keycode from 'keycode'; import React from 'react'; import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; @@ -9,7 +8,7 @@ import { bsClass, getClassSet, prefix, - splitBsPropsAndOmit + splitBsPropsAndOmit, } from './utils/bootstrapUtils'; import createChainedFunction from './utils/createChainedFunction'; import ValidComponentChildren from './utils/ValidComponentChildren'; @@ -20,12 +19,12 @@ const propTypes = { onClose: PropTypes.func, labelledBy: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), onSelect: PropTypes.func, - rootCloseEvent: PropTypes.oneOf(['click', 'mousedown']) + rootCloseEvent: PropTypes.oneOf(['click', 'mousedown']), }; const defaultProps = { bsRole: 'menu', - pullRight: false + pullRight: false, }; class DropdownMenu extends React.Component { @@ -73,17 +72,20 @@ class DropdownMenu extends React.Component { } handleKeyDown(event) { - switch (event.keyCode) { - case keycode.codes.down: + switch (event.key) { + case 'Down': + case 'ArrowDown': this.focusNext(); event.preventDefault(); break; - case keycode.codes.up: + case 'Up': + case 'ArrowUp': this.focusPrevious(); event.preventDefault(); break; - case keycode.codes.esc: - case keycode.codes.tab: + case 'Esc': + case 'Escape': + case 'Tab': this.props.onClose(event, { source: 'keydown' }); break; default: @@ -110,7 +112,7 @@ class DropdownMenu extends React.Component { const classes = { ...getClassSet(bsProps), - [prefix(bsProps, 'right')]: pullRight + [prefix(bsProps, 'right')]: pullRight, }; return ( @@ -125,14 +127,14 @@ class DropdownMenu extends React.Component { className={classNames(className, classes)} aria-labelledby={labelledBy} > - {ValidComponentChildren.map(children, child => + {ValidComponentChildren.map(children, (child) => React.cloneElement(child, { onKeyDown: createChainedFunction( child.props.onKeyDown, - this.handleKeyDown + this.handleKeyDown, ), - onSelect: createChainedFunction(child.props.onSelect, onSelect) - }) + onSelect: createChainedFunction(child.props.onSelect, onSelect), + }), )} diff --git a/fork/react-bootstrap/src/DropdownMenu.test.js b/fork/react-bootstrap/src/DropdownMenu.test.js index 5a5eb908e09..ef69a260936 100644 --- a/fork/react-bootstrap/src/DropdownMenu.test.js +++ b/fork/react-bootstrap/src/DropdownMenu.test.js @@ -1,4 +1,3 @@ -// import keycode from 'keycode'; // import ReactDOM from 'react-dom'; // import ReactTestUtils from 'react-dom/test-utils'; import { screen, render } from '@testing-library/react'; @@ -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 9aa6123e63d..350a818a084 100644 --- a/fork/react-bootstrap/src/Nav.js +++ b/fork/react-bootstrap/src/Nav.js @@ -1,18 +1,11 @@ import classNames from 'classnames'; -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 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 +17,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 `