From d2affeddd2d00e10f9bd3a9347b50db4457c6300 Mon Sep 17 00:00:00 2001 From: Eric Olkowski Date: Fri, 26 Apr 2024 09:12:53 -0400 Subject: [PATCH 1/6] feat(Dropdown): Added simple template --- .../components/Dropdown/DropdownSimple.tsx | 121 ++++++++++++++++++ .../examples/DropdownSimpleExample.tsx | 53 ++++++++ .../Dropdown/examples/DropdownTemplates.md | 23 ++++ .../src/components/Dropdown/index.ts | 1 + .../react-templates/src/components/index.ts | 1 + 5 files changed, 199 insertions(+) create mode 100644 packages/react-templates/src/components/Dropdown/DropdownSimple.tsx create mode 100644 packages/react-templates/src/components/Dropdown/examples/DropdownSimpleExample.tsx create mode 100644 packages/react-templates/src/components/Dropdown/examples/DropdownTemplates.md create mode 100644 packages/react-templates/src/components/Dropdown/index.ts diff --git a/packages/react-templates/src/components/Dropdown/DropdownSimple.tsx b/packages/react-templates/src/components/Dropdown/DropdownSimple.tsx new file mode 100644 index 00000000000..9c757f1248e --- /dev/null +++ b/packages/react-templates/src/components/Dropdown/DropdownSimple.tsx @@ -0,0 +1,121 @@ +import React from 'react'; +import { + Dropdown, + DropdownItem, + DropdownList, + DropdownItemProps +} from '@patternfly/react-core/dist/esm/components/Dropdown'; +import { MenuToggle, MenuToggleElement } from '@patternfly/react-core/dist/esm/components/MenuToggle'; +import { Divider } from '@patternfly/react-core/dist/esm/components/Divider'; + +export interface DropdownSimpleItem extends Omit { + /** Content of the dropdown item. If the isDivider prop is true, this prop will be ignored. */ + content?: React.ReactNode; + /** Unique identifier for the dropdown item, which is used in the dropdown onSelect callback */ + value: string | number; + /** Callback for when the dropdown item is clicked. */ + onClick?: (event?: any) => void; + /** URL to redirect to when the dropdown item is clicked. */ + to?: string; + /** Flag indicating whether the dropdown item should render as a divider. If true, the item will be rendered without + * the dropdown item wrapper. + */ + isDivider?: boolean; +} + +export interface DropdownSimpleProps { + /** Initial items of the dropdown. */ + initialItems?: DropdownSimpleItem[]; + /** @hide Forwarded ref */ + innerRef?: React.Ref; + /** Flag indicating the dropdown should be disabled. */ + isDisabled?: boolean; + /** Flag indicated whether the dropdown toggle should take up the full width of its parent. */ + isToggleFullWidth?: boolean; + /** Callback triggered when any dropdown item is clicked. */ + onSelect?: (event?: React.MouseEvent, value?: string | number) => void; + /** Callback triggered when the dropdown toggle opens or closes. */ + onToggle?: (nextIsOpen: boolean) => void; + /** Flag indicating the dropdown toggle should be focused after a dropdown item is clicked. */ + shouldFocusToggleOnSelect?: boolean; + /** Adds an accessible name to the dropdown toggle. Required when the dropdown toggle does not + * have any text content. + */ + toggleAriaLabel?: string; + /** Content of the toggle. */ + toggleContent: React.ReactNode; + /** Variant style of the dropdown toggle. */ + toggleVariant?: 'default' | 'plain' | 'plainText'; +} + +const DropdownSimpleBase: React.FC = ({ + innerRef, + initialItems, + onSelect: onSelectProp, + onToggle: onToggleProp, + isDisabled, + toggleAriaLabel, + toggleContent, + isToggleFullWidth, + toggleVariant = 'default', + shouldFocusToggleOnSelect, + ...props +}: DropdownSimpleProps) => { + const [isOpen, setIsOpen] = React.useState(false); + + const onSelect = (event: React.MouseEvent, value: string | number) => { + onSelectProp && onSelectProp(event, value); + setIsOpen(false); + }; + + const onToggle = () => { + onToggleProp && onToggleProp(!isOpen); + setIsOpen(!isOpen); + }; + + const dropdownToggle = (toggleRef: React.Ref) => ( + + {toggleContent} + + ); + + const dropdownSimpleItems = initialItems.map((item) => { + const { content, onClick, to, value, isDivider, ...itemProps } = item; + + return isDivider ? ( + + ) : ( + + {content} + + ); + }); + + return ( + setIsOpen(isOpen)} + ref={innerRef} + {...props} + > + {dropdownSimpleItems} + + ); +}; + +export const DropdownSimple = React.forwardRef((props: DropdownSimpleProps, ref: React.Ref) => ( + +)); + +DropdownSimple.displayName = 'DropdownSimple'; diff --git a/packages/react-templates/src/components/Dropdown/examples/DropdownSimpleExample.tsx b/packages/react-templates/src/components/Dropdown/examples/DropdownSimpleExample.tsx new file mode 100644 index 00000000000..08d16b69fe9 --- /dev/null +++ b/packages/react-templates/src/components/Dropdown/examples/DropdownSimpleExample.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { Checkbox, Flex, FlexItem } from '@patternfly/react-core'; +import { DropdownSimple, DropdownSimpleItemProps } from '@patternfly/react-templates'; +import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; + +export const DropdownSimpleExample: React.FC = () => { + const [isDisabled, setIsDisabled] = React.useState(false); + + const items: DropdownSimpleItemProps[] = [ + // eslint-disable-next-line no-console + { content: 'Action', value: 1, onClick: () => console.log('Action clicked') }, + // Prevent default click behavior on link for example purposes + { content: 'Link', value: 2, to: '#', onClick: (event: any) => event.preventDefault() }, + { content: 'Disabled Action', value: 3, isDisabled: true }, + { value: 'separator', isDivider: true }, + // eslint-disable-next-line no-console + { content: 'Second action', value: 4, onClick: () => console.log('Second action clicked') } + ]; + + return ( + + , checked: boolean) => setIsDisabled(checked)} + style={{ marginBottom: 20 }} + /> + + + + + + + + + + + + ); +}; diff --git a/packages/react-templates/src/components/Dropdown/examples/DropdownTemplates.md b/packages/react-templates/src/components/Dropdown/examples/DropdownTemplates.md new file mode 100644 index 00000000000..fdc51080eec --- /dev/null +++ b/packages/react-templates/src/components/Dropdown/examples/DropdownTemplates.md @@ -0,0 +1,23 @@ +--- +id: Dropdown +section: components +subsection: menus +template: true +beta: true +propComponents: ['DropdownSimple', 'DropdownSimpleItem'] +--- + +Note: Templates live in their own package at [@patternfly/react-templates](https://www.npmjs.com/package/@patternfly/react-templates)! + +For custom use cases, please see the dropdown component suite from [@patternfly/react-core](https://www.npmjs.com/package/@patternfly/react-core). + +import { DropdownSimple } from '@patternfly/react-templates'; +import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; + +## Examples + +### Simple + +```ts file="./DropdownSimpleExample.tsx" + +``` diff --git a/packages/react-templates/src/components/Dropdown/index.ts b/packages/react-templates/src/components/Dropdown/index.ts new file mode 100644 index 00000000000..d56944e88c9 --- /dev/null +++ b/packages/react-templates/src/components/Dropdown/index.ts @@ -0,0 +1 @@ +export * from './DropdownSimple'; diff --git a/packages/react-templates/src/components/index.ts b/packages/react-templates/src/components/index.ts index 7868ecbae29..938364392c1 100644 --- a/packages/react-templates/src/components/index.ts +++ b/packages/react-templates/src/components/index.ts @@ -1 +1,2 @@ +export * from './Dropdown'; export * from './Select'; From c0ebdf0350f87a320aec748857ce9aaedb544ace Mon Sep 17 00:00:00 2001 From: Eric Olkowski Date: Fri, 26 Apr 2024 10:03:43 -0400 Subject: [PATCH 2/6] Added tests --- .../components/Dropdown/DropdownSimple.tsx | 2 +- .../__tests__/DropdownSimple.test.tsx | 213 ++++++++++++++++++ .../DropdownSimple.test.tsx.snap | 147 ++++++++++++ 3 files changed, 361 insertions(+), 1 deletion(-) create mode 100644 packages/react-templates/src/components/Dropdown/__tests__/DropdownSimple.test.tsx create mode 100644 packages/react-templates/src/components/Dropdown/__tests__/__snapshots__/DropdownSimple.test.tsx.snap diff --git a/packages/react-templates/src/components/Dropdown/DropdownSimple.tsx b/packages/react-templates/src/components/Dropdown/DropdownSimple.tsx index 9c757f1248e..e6ac5cd36e9 100644 --- a/packages/react-templates/src/components/Dropdown/DropdownSimple.tsx +++ b/packages/react-templates/src/components/Dropdown/DropdownSimple.tsx @@ -87,7 +87,7 @@ const DropdownSimpleBase: React.FC = ({ ); - const dropdownSimpleItems = initialItems.map((item) => { + const dropdownSimpleItems = initialItems?.map((item) => { const { content, onClick, to, value, isDivider, ...itemProps } = item; return isDivider ? ( diff --git a/packages/react-templates/src/components/Dropdown/__tests__/DropdownSimple.test.tsx b/packages/react-templates/src/components/Dropdown/__tests__/DropdownSimple.test.tsx new file mode 100644 index 00000000000..fbb0884ec6e --- /dev/null +++ b/packages/react-templates/src/components/Dropdown/__tests__/DropdownSimple.test.tsx @@ -0,0 +1,213 @@ +import * as React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { DropdownSimple } from '../DropdownSimple'; +import styles from '@patternfly/react-styles/css/components/MenuToggle/menu-toggle'; + +describe('Dropdown toggle', () => { + test('Renders dropdown toggle as not disabled when isDisabled is not true', () => { + render(); + + expect(screen.getByRole('button', { name: 'Dropdown' })).not.toBeDisabled(); + }); + + test('Renders dropdown toggle as disabled when isDisabled is true', () => { + render(); + + expect(screen.getByRole('button', { name: 'Dropdown' })).toBeDisabled(); + }); + + test('Passes toggleVariant', () => { + render(); + + expect(screen.getByRole('button', { name: 'Dropdown' })).toHaveClass(styles.modifiers.plain); + }); + + test('Passes toggleAriaLabel', () => { + render(); + + expect(screen.getByRole('button')).toHaveAccessibleName('Aria label content'); + }); + + test('Calls onToggle with next isOpen state when dropdown toggle is clicked', async () => { + const onToggle = jest.fn(); + const user = userEvent.setup(); + render(); + + const toggle = screen.getByRole('button', { name: 'Dropdown' }); + await user.click(toggle); + expect(onToggle).toHaveBeenCalledWith(true); + }); + + test('Only calls onToggle when dropdown toggle is clicked', async () => { + const onToggle = jest.fn(); + const items = [{ content: 'Action', value: 1 }]; + const user = userEvent.setup(); + render(); + + const toggle = screen.getByRole('button', { name: 'Dropdown' }); + await user.click(toggle); + const actionItem = screen.getByRole('menuitem', { name: 'Action' }); + await user.click(actionItem); + expect(onToggle).toHaveBeenCalledTimes(1); + }); + + test('Calls toggle onSelect when item is clicked', async () => { + const onSelect = jest.fn(); + const items = [{ content: 'Action', value: 1 }]; + const user = userEvent.setup(); + render(); + + const toggle = screen.getByRole('button', { name: 'Dropdown' }); + await user.click(toggle); + + const actionItem = screen.getByRole('menuitem', { name: 'Action' }); + await user.click(actionItem); + expect(onSelect).toHaveBeenCalledTimes(1); + }); + + test('Does not call toggle onSelect when item is not clicked', async () => { + const onSelect = jest.fn(); + const items = [{ content: 'Action', value: 1 }]; + const user = userEvent.setup(); + render(); + + const toggle = screen.getByRole('button', { name: 'Dropdown' }); + await user.click(toggle); + await user.click(toggle); + expect(onSelect).not.toHaveBeenCalled(); + }); + + test('Matches snapshot', () => { + const { asFragment } = render(); + + expect(asFragment()).toMatchSnapshot(); + }); +}); + +describe('Dropdown items', () => { + test('Renders with items', async () => { + const items = [ + { content: 'Action', value: 1 }, + { value: 'separator', isDivider: true } + ]; + const user = userEvent.setup(); + render(); + + const toggle = screen.getByRole('button', { name: 'Dropdown' }); + await user.click(toggle); + + const actionItem = screen.getByRole('menuitem', { name: 'Action' }); + const dividerItem = screen.getByRole('separator'); + expect(actionItem).toBeInTheDocument(); + expect(dividerItem).toBeInTheDocument(); + }); + + test('Renders with a link item', async () => { + const items = [{ content: 'Link', value: 1, to: '#' }]; + const user = userEvent.setup(); + render(); + + const toggle = screen.getByRole('button', { name: 'Dropdown' }); + await user.click(toggle); + + const linkItem = screen.getByRole('menuitem', { name: 'Link' }); + expect(linkItem.getAttribute('href')).toBe('#'); + }); + + test('Renders with items not disabled by default', async () => { + const items = [{ content: 'Action', value: 1 }]; + const user = userEvent.setup(); + render(); + + const toggle = screen.getByRole('button', { name: 'Dropdown' }); + await user.click(toggle); + + const actionItem = screen.getByRole('menuitem', { name: 'Action' }); + expect(actionItem).not.toBeDisabled(); + }); + + test('Renders with a disabled item', async () => { + const items = [{ content: 'Action', value: 1, isDisabled: true }]; + const user = userEvent.setup(); + render(); + + const toggle = screen.getByRole('button', { name: 'Dropdown' }); + await user.click(toggle); + + const actionItem = screen.getByRole('menuitem', { name: 'Action' }); + expect(actionItem).toBeDisabled(); + }); + + test('Spreads props on item', async () => { + const items = [{ content: 'Action', value: 1, id: 'Test' }]; + const user = userEvent.setup(); + render(); + + const toggle = screen.getByRole('button', { name: 'Dropdown' }); + await user.click(toggle); + + const actionItem = screen.getByRole('menuitem', { name: 'Action' }); + expect(actionItem.getAttribute('id')).toBe('Test'); + }); + + test('Calls item onClick when clicked', async () => { + const onClick = jest.fn(); + const items = [{ content: 'Action', value: 1, onClick }]; + const user = userEvent.setup(); + render(); + + const toggle = screen.getByRole('button', { name: 'Dropdown' }); + await user.click(toggle); + + const actionItem = screen.getByRole('menuitem', { name: 'Action' }); + await user.click(actionItem); + expect(onClick).toHaveBeenCalledTimes(1); + }); + + test('Does not call item onClick when not clicked', async () => { + const onClick = jest.fn(); + const items = [ + { content: 'Action', value: 1, onClick }, + { content: 'Action 2', value: 2 } + ]; + const user = userEvent.setup(); + render(); + + const toggle = screen.getByRole('button', { name: 'Dropdown' }); + await user.click(toggle); + + const actionItem = screen.getByRole('menuitem', { name: 'Action 2' }); + await user.click(actionItem); + expect(onClick).not.toHaveBeenCalled(); + }); + + test('Does not call item onClick when clicked and item is disabled', async () => { + const onClick = jest.fn(); + const items = [{ content: 'Action', value: 1, onClick, isDisabled: true }]; + const user = userEvent.setup(); + render(); + + const toggle = screen.getByRole('button', { name: 'Dropdown' }); + await user.click(toggle); + + const actionItem = screen.getByRole('menuitem', { name: 'Action' }); + await user.click(actionItem); + expect(onClick).not.toHaveBeenCalled(); + }); + + test('Matches snapshot', async () => { + const items = [ + { content: 'Action', value: 1 }, + { value: 'separator', isDivider: true }, + { content: 'Link', value: 'separator', to: '#' } + ]; + const user = userEvent.setup(); + const { asFragment } = render(); + + const toggle = screen.getByRole('button', { name: 'Dropdown' }); + await user.click(toggle); + + expect(asFragment()).toMatchSnapshot(); + }); +}); diff --git a/packages/react-templates/src/components/Dropdown/__tests__/__snapshots__/DropdownSimple.test.tsx.snap b/packages/react-templates/src/components/Dropdown/__tests__/__snapshots__/DropdownSimple.test.tsx.snap new file mode 100644 index 00000000000..01afc33b7c1 --- /dev/null +++ b/packages/react-templates/src/components/Dropdown/__tests__/__snapshots__/DropdownSimple.test.tsx.snap @@ -0,0 +1,147 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Dropdown items Matches snapshot 1`] = ` + + +
+
+ +
+
+
+`; + +exports[`Dropdown toggle Matches snapshot 1`] = ` + + + +`; From cbadb61fdc9375459f4da6043fd4a614c1c3bf60 Mon Sep 17 00:00:00 2001 From: Eric Olkowski Date: Fri, 26 Apr 2024 10:30:20 -0400 Subject: [PATCH 3/6] Added imports to example file --- .../Dropdown/examples/DropdownTemplates.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/react-templates/src/components/Dropdown/examples/DropdownTemplates.md b/packages/react-templates/src/components/Dropdown/examples/DropdownTemplates.md index fdc51080eec..2eab76c094a 100644 --- a/packages/react-templates/src/components/Dropdown/examples/DropdownTemplates.md +++ b/packages/react-templates/src/components/Dropdown/examples/DropdownTemplates.md @@ -11,6 +11,18 @@ Note: Templates live in their own package at [@patternfly/react-templates](https For custom use cases, please see the dropdown component suite from [@patternfly/react-core](https://www.npmjs.com/package/@patternfly/react-core). +import { +Checkbox, +Divider, +Dropdown, +DropdownItem, +DropdownList, +DropdownItemProps, +Flex, +FlexItem, +MenuToggle, +MenuToggleElement +} from '@patternfly/react-core'; import { DropdownSimple } from '@patternfly/react-templates'; import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; From a3588458adca831dc365aed94cd93f6d3d7967eb Mon Sep 17 00:00:00 2001 From: Eric Olkowski Date: Fri, 26 Apr 2024 15:04:36 -0400 Subject: [PATCH 4/6] Additional fixes for docs fail --- .../react-templates/src/components/Dropdown/DropdownSimple.tsx | 2 +- .../src/components/Dropdown/examples/DropdownSimpleExample.tsx | 2 +- .../src/components/Dropdown/examples/DropdownTemplates.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-templates/src/components/Dropdown/DropdownSimple.tsx b/packages/react-templates/src/components/Dropdown/DropdownSimple.tsx index e6ac5cd36e9..d5abe602396 100644 --- a/packages/react-templates/src/components/Dropdown/DropdownSimple.tsx +++ b/packages/react-templates/src/components/Dropdown/DropdownSimple.tsx @@ -48,7 +48,7 @@ export interface DropdownSimpleProps { toggleVariant?: 'default' | 'plain' | 'plainText'; } -const DropdownSimpleBase: React.FC = ({ +const DropdownSimpleBase: React.FunctionComponent = ({ innerRef, initialItems, onSelect: onSelectProp, diff --git a/packages/react-templates/src/components/Dropdown/examples/DropdownSimpleExample.tsx b/packages/react-templates/src/components/Dropdown/examples/DropdownSimpleExample.tsx index 08d16b69fe9..9e5cf47595c 100644 --- a/packages/react-templates/src/components/Dropdown/examples/DropdownSimpleExample.tsx +++ b/packages/react-templates/src/components/Dropdown/examples/DropdownSimpleExample.tsx @@ -3,7 +3,7 @@ import { Checkbox, Flex, FlexItem } from '@patternfly/react-core'; import { DropdownSimple, DropdownSimpleItemProps } from '@patternfly/react-templates'; import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; -export const DropdownSimpleExample: React.FC = () => { +export const DropdownSimpleExample: React.FunctionComponent = () => { const [isDisabled, setIsDisabled] = React.useState(false); const items: DropdownSimpleItemProps[] = [ diff --git a/packages/react-templates/src/components/Dropdown/examples/DropdownTemplates.md b/packages/react-templates/src/components/Dropdown/examples/DropdownTemplates.md index 2eab76c094a..fa9f561423b 100644 --- a/packages/react-templates/src/components/Dropdown/examples/DropdownTemplates.md +++ b/packages/react-templates/src/components/Dropdown/examples/DropdownTemplates.md @@ -23,7 +23,7 @@ FlexItem, MenuToggle, MenuToggleElement } from '@patternfly/react-core'; -import { DropdownSimple } from '@patternfly/react-templates'; +import { DropdownSimple, DropdownSimpleItemProps } from '@patternfly/react-templates'; import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; ## Examples From eef8dd297f3e7c15eed90e3654854a9f25b53f4b Mon Sep 17 00:00:00 2001 From: Eric Olkowski Date: Fri, 26 Apr 2024 15:36:38 -0400 Subject: [PATCH 5/6] Updated import name --- .../components/Dropdown/examples/DropdownSimpleExample.tsx | 4 ++-- .../src/components/Dropdown/examples/DropdownTemplates.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-templates/src/components/Dropdown/examples/DropdownSimpleExample.tsx b/packages/react-templates/src/components/Dropdown/examples/DropdownSimpleExample.tsx index 9e5cf47595c..6129f7ff33f 100644 --- a/packages/react-templates/src/components/Dropdown/examples/DropdownSimpleExample.tsx +++ b/packages/react-templates/src/components/Dropdown/examples/DropdownSimpleExample.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { Checkbox, Flex, FlexItem } from '@patternfly/react-core'; -import { DropdownSimple, DropdownSimpleItemProps } from '@patternfly/react-templates'; +import { DropdownSimple, DropdownSimpleItem } from '@patternfly/react-templates'; import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; export const DropdownSimpleExample: React.FunctionComponent = () => { const [isDisabled, setIsDisabled] = React.useState(false); - const items: DropdownSimpleItemProps[] = [ + const items: DropdownSimpleItem[] = [ // eslint-disable-next-line no-console { content: 'Action', value: 1, onClick: () => console.log('Action clicked') }, // Prevent default click behavior on link for example purposes diff --git a/packages/react-templates/src/components/Dropdown/examples/DropdownTemplates.md b/packages/react-templates/src/components/Dropdown/examples/DropdownTemplates.md index fa9f561423b..9261adf60b2 100644 --- a/packages/react-templates/src/components/Dropdown/examples/DropdownTemplates.md +++ b/packages/react-templates/src/components/Dropdown/examples/DropdownTemplates.md @@ -23,7 +23,7 @@ FlexItem, MenuToggle, MenuToggleElement } from '@patternfly/react-core'; -import { DropdownSimple, DropdownSimpleItemProps } from '@patternfly/react-templates'; +import { DropdownSimple, DropdownSimpleItem } from '@patternfly/react-templates'; import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; ## Examples From c96acf4c4bf834567a8de4792a805c5a2a47b7b4 Mon Sep 17 00:00:00 2001 From: Eric Olkowski Date: Mon, 29 Apr 2024 09:07:53 -0400 Subject: [PATCH 6/6] Added additional tests --- .../components/Dropdown/DropdownSimple.tsx | 3 +- .../__tests__/DropdownSimple.test.tsx | 63 +++++++++++++++---- .../DropdownSimple.test.tsx.snap | 6 +- .../Dropdown/examples/DropdownTemplates.md | 3 +- 4 files changed, 59 insertions(+), 16 deletions(-) diff --git a/packages/react-templates/src/components/Dropdown/DropdownSimple.tsx b/packages/react-templates/src/components/Dropdown/DropdownSimple.tsx index d5abe602396..7f2c773e92e 100644 --- a/packages/react-templates/src/components/Dropdown/DropdownSimple.tsx +++ b/packages/react-templates/src/components/Dropdown/DropdownSimple.tsx @@ -7,6 +7,7 @@ import { } from '@patternfly/react-core/dist/esm/components/Dropdown'; import { MenuToggle, MenuToggleElement } from '@patternfly/react-core/dist/esm/components/MenuToggle'; import { Divider } from '@patternfly/react-core/dist/esm/components/Divider'; +import { OUIAProps } from '@patternfly/react-core/dist/esm/helpers'; export interface DropdownSimpleItem extends Omit { /** Content of the dropdown item. If the isDivider prop is true, this prop will be ignored. */ @@ -23,7 +24,7 @@ export interface DropdownSimpleItem extends Omit { isDivider?: boolean; } -export interface DropdownSimpleProps { +export interface DropdownSimpleProps extends OUIAProps { /** Initial items of the dropdown. */ initialItems?: DropdownSimpleItem[]; /** @hide Forwarded ref */ diff --git a/packages/react-templates/src/components/Dropdown/__tests__/DropdownSimple.test.tsx b/packages/react-templates/src/components/Dropdown/__tests__/DropdownSimple.test.tsx index fbb0884ec6e..ddd8b37df66 100644 --- a/packages/react-templates/src/components/Dropdown/__tests__/DropdownSimple.test.tsx +++ b/packages/react-templates/src/components/Dropdown/__tests__/DropdownSimple.test.tsx @@ -39,17 +39,20 @@ describe('Dropdown toggle', () => { expect(onToggle).toHaveBeenCalledWith(true); }); - test('Only calls onToggle when dropdown toggle is clicked', async () => { + test('Does not call onToggle when dropdown toggle is not clicked', async () => { const onToggle = jest.fn(); const items = [{ content: 'Action', value: 1 }]; const user = userEvent.setup(); - render(); + render( +
+ + +
+ ); - const toggle = screen.getByRole('button', { name: 'Dropdown' }); - await user.click(toggle); - const actionItem = screen.getByRole('menuitem', { name: 'Action' }); - await user.click(actionItem); - expect(onToggle).toHaveBeenCalledTimes(1); + const btn = screen.getByRole('button', { name: 'Actual' }); + await user.click(btn); + expect(onToggle).not.toHaveBeenCalled(); }); test('Calls toggle onSelect when item is clicked', async () => { @@ -78,6 +81,44 @@ describe('Dropdown toggle', () => { expect(onSelect).not.toHaveBeenCalled(); }); + test('Does not pass isToggleFullWidth to menu toggle by default', () => { + render(); + + expect(screen.getByRole('button', { name: 'Dropdown' })).not.toHaveClass(styles.modifiers.fullWidth); + }); + + test('Passes isToggleFullWidth to menu toggle when passed in', () => { + render(); + + expect(screen.getByRole('button', { name: 'Dropdown' })).toHaveClass(styles.modifiers.fullWidth); + }); + + test('Does not focus toggle on item select by default', async () => { + const items = [{ content: 'Action', value: 1 }]; + const user = userEvent.setup(); + render(); + + const toggle = screen.getByRole('button', { name: 'Dropdown' }); + await user.click(toggle); + const actionItem = screen.getByRole('menuitem', { name: 'Action' }); + await user.click(actionItem); + + expect(toggle).not.toHaveFocus(); + }); + + test('Focuses toggle on item select when shouldFocusToggleOnSelect is true', async () => { + const items = [{ content: 'Action', value: 1 }]; + const user = userEvent.setup(); + render(); + + const toggle = screen.getByRole('button', { name: 'Dropdown' }); + await user.click(toggle); + const actionItem = screen.getByRole('menuitem', { name: 'Action' }); + await user.click(actionItem); + + expect(toggle).toHaveFocus(); + }); + test('Matches snapshot', () => { const { asFragment } = render(); @@ -198,12 +239,12 @@ describe('Dropdown items', () => { test('Matches snapshot', async () => { const items = [ - { content: 'Action', value: 1 }, - { value: 'separator', isDivider: true }, - { content: 'Link', value: 'separator', to: '#' } + { content: 'Action', value: 1, ouiaId: '1' }, + { value: 'separator', isDivider: true, ouiaId: '2' }, + { content: 'Link', value: 'separator', to: '#', ouiaId: '3' } ]; const user = userEvent.setup(); - const { asFragment } = render(); + const { asFragment } = render(); const toggle = screen.getByRole('button', { name: 'Dropdown' }); await user.click(toggle); diff --git a/packages/react-templates/src/components/Dropdown/__tests__/__snapshots__/DropdownSimple.test.tsx.snap b/packages/react-templates/src/components/Dropdown/__tests__/__snapshots__/DropdownSimple.test.tsx.snap index 01afc33b7c1..c1f0b5cab98 100644 --- a/packages/react-templates/src/components/Dropdown/__tests__/__snapshots__/DropdownSimple.test.tsx.snap +++ b/packages/react-templates/src/components/Dropdown/__tests__/__snapshots__/DropdownSimple.test.tsx.snap @@ -36,7 +36,7 @@ exports[`Dropdown items Matches snapshot 1`] = `