@patternfly/chatbot 6.4.0-prerelease.14 → 6.4.0-prerelease.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +1 -1
  2. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.js +6 -6
  3. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +13 -12
  4. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +17 -5
  5. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +24 -20
  6. package/dist/cjs/SourcesCard/SourcesCard.d.ts +13 -1
  7. package/dist/cjs/SourcesCard/SourcesCard.js +6 -6
  8. package/dist/cjs/SourcesCard/SourcesCard.test.js +49 -0
  9. package/dist/css/main.css +43 -30
  10. package/dist/css/main.css.map +1 -1
  11. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +1 -1
  12. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.js +6 -6
  13. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +13 -12
  14. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +18 -6
  15. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +24 -20
  16. package/dist/esm/SourcesCard/SourcesCard.d.ts +13 -1
  17. package/dist/esm/SourcesCard/SourcesCard.js +6 -6
  18. package/dist/esm/SourcesCard/SourcesCard.test.js +50 -1
  19. package/package.json +2 -2
  20. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithSources.tsx +70 -0
  21. package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +1 -1
  22. package/patternfly-docs/patternfly-docs.config.js +1 -1
  23. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.tsx +6 -6
  24. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.tsx +1 -0
  25. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +28 -36
  26. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +32 -18
  27. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +74 -60
  28. package/src/SourcesCard/SourcesCard.scss +17 -0
  29. package/src/SourcesCard/SourcesCard.test.tsx +93 -0
  30. package/src/SourcesCard/SourcesCard.tsx +116 -80
@@ -17,11 +17,11 @@ describe('ChatbotConversationHistoryDropdown', () => {
17
17
  const menuItems = (_jsxs(_Fragment, { children: [_jsx(DropdownItem, { children: "Rename" }), _jsx(DropdownItem, { children: "Delete" })] }));
18
18
  it('should render the dropdown', () => {
19
19
  render(_jsx(ChatbotConversationHistoryDropdown, { menuItems: menuItems, menuClassName: "custom-class" }));
20
- expect(screen.queryByRole('button', { name: /Conversation options/i })).toBeInTheDocument();
20
+ expect(screen.queryByRole('menuitem', { name: /Conversation options/i })).toBeInTheDocument();
21
21
  });
22
22
  it('should display the dropdown menuItems', () => {
23
23
  render(_jsx(ChatbotConversationHistoryDropdown, { menuItems: menuItems }));
24
- const toggle = screen.queryByRole('button', { name: /Conversation options/i });
24
+ const toggle = screen.queryByRole('menuitem', { name: /Conversation options/i });
25
25
  expect(toggle).toBeInTheDocument();
26
26
  fireEvent.click(toggle);
27
27
  waitFor(() => {
@@ -31,14 +31,14 @@ describe('ChatbotConversationHistoryDropdown', () => {
31
31
  });
32
32
  it('should invoke onSelect callback when menuitem is clicked', () => {
33
33
  render(_jsx(ChatbotConversationHistoryDropdown, { menuItems: menuItems, onSelect: onSelect }));
34
- const toggle = screen.queryByRole('button', { name: /Conversation options/i });
34
+ const toggle = screen.queryByRole('menuitem', { name: /Conversation options/i });
35
35
  fireEvent.click(toggle);
36
36
  fireEvent.click(screen.getByText('Rename'));
37
37
  expect(onSelect).toHaveBeenCalled();
38
38
  });
39
39
  it('should toggle the dropdown when menuitem is clicked', () => {
40
40
  render(_jsx(ChatbotConversationHistoryDropdown, { menuItems: menuItems, onSelect: onSelect }));
41
- const toggle = screen.queryByRole('button', { name: /Conversation options/i });
41
+ const toggle = screen.queryByRole('menuitem', { name: /Conversation options/i });
42
42
  fireEvent.click(toggle);
43
43
  fireEvent.click(screen.getByText('Delete'));
44
44
  expect(onSelect).toHaveBeenCalled();
@@ -46,7 +46,7 @@ describe('ChatbotConversationHistoryDropdown', () => {
46
46
  });
47
47
  it('should close the dropdown when user clicks outside', () => {
48
48
  render(_jsx(ChatbotConversationHistoryDropdown, { menuItems: menuItems, onSelect: onSelect }));
49
- const toggle = screen.queryByRole('button', { name: /Conversation options/i });
49
+ const toggle = screen.queryByRole('menuitem', { name: /Conversation options/i });
50
50
  fireEvent.click(toggle);
51
51
  expect(screen.queryByText('Delete')).toBeInTheDocument();
52
52
  fireEvent.click(toggle.parentElement);
@@ -54,7 +54,7 @@ describe('ChatbotConversationHistoryDropdown', () => {
54
54
  });
55
55
  it('should show the tooltip when the user hovers over the toggle button', () => __awaiter(void 0, void 0, void 0, function* () {
56
56
  render(_jsx(ChatbotConversationHistoryDropdown, { menuItems: menuItems, label: "Actions dropdown" }));
57
- const toggle = screen.queryByRole('button', { name: /Actions dropdown/i });
57
+ const toggle = screen.queryByRole('menuitem', { name: /Actions dropdown/i });
58
58
  fireEvent(toggle, new MouseEvent('mouseenter', {
59
59
  bubbles: false,
60
60
  cancelable: false
@@ -1,6 +1,5 @@
1
1
  import type { FunctionComponent } from 'react';
2
- import { ButtonProps, DrawerProps, ListItemProps, DrawerPanelContentProps, DrawerContentProps, DrawerContentBodyProps, DrawerHeadProps, DrawerActionsProps, DrawerCloseButtonProps, DrawerPanelBodyProps, SkeletonProps, MenuProps, // Remove in next breaking change
3
- TitleProps, ListProps, SearchInputProps } from '@patternfly/react-core';
2
+ import { ButtonProps, DrawerProps, DrawerPanelContentProps, DrawerContentProps, DrawerContentBodyProps, DrawerHeadProps, DrawerActionsProps, DrawerCloseButtonProps, DrawerPanelBodyProps, SkeletonProps, MenuProps, TitleProps, MenuListProps, SearchInputProps, MenuItemProps, MenuGroupProps, MenuContentProps } from '@patternfly/react-core';
4
3
  import { ChatbotDisplayMode } from '../Chatbot/Chatbot';
5
4
  import { HistoryEmptyStateProps } from './EmptyState';
6
5
  export interface Conversation {
@@ -20,10 +19,8 @@ export interface Conversation {
20
19
  label?: string;
21
20
  /** Callback for when user selects item. */
22
21
  onSelect?: (event?: React.MouseEvent, value?: string | number) => void;
23
- /** Additional props passed to conversation button item */
24
- additionalProps?: ButtonProps;
25
- /** Additional props passed to conversation list item */
26
- listItemProps?: Omit<ListItemProps, 'children'>;
22
+ /** Additional props passed to menu item */
23
+ additionalProps?: MenuItemProps;
27
24
  /** Custom dropdown ID to ensure uniqueness across demo instances */
28
25
  dropdownId?: string;
29
26
  }
@@ -43,11 +40,13 @@ export interface ChatbotConversationHistoryNavProps extends DrawerProps {
43
40
  };
44
41
  /** Additional button props for new chat button. */
45
42
  newChatButtonProps?: ButtonProps;
46
- /** Additional props applied to all conversation list headers */
47
- listTitleProps?: Partial<TitleProps>;
48
- /** Additional props applied to conversation list. If conversations is an object, you should pass an object of ListProps for each group. */
49
- listProps?: ListProps | {
50
- [key: string]: ListProps;
43
+ /** Additional props applied to conversation menu group. If conversations is an object, you should pass an object of MenuGroupProps for each group. */
44
+ menuGroupProps?: MenuGroupProps | {
45
+ [key: string]: MenuGroupProps;
46
+ };
47
+ /** Additional props applied to conversation list. If conversations is an object, you should pass an object of MenuListProps for each group. */
48
+ menuListProps?: Omit<MenuListProps, 'children'> | {
49
+ [key: string]: Omit<MenuListProps, 'children'>;
51
50
  };
52
51
  /** Text shown in blue button */
53
52
  newChatButtonText?: string;
@@ -69,7 +68,7 @@ export interface ChatbotConversationHistoryNavProps extends DrawerProps {
69
68
  reverseButtonOrder?: boolean;
70
69
  /** Custom test id for the drawer actions */
71
70
  drawerActionsTestId?: string;
72
- /** @deprecated Additional props applied to list container */
71
+ /** Additional props applied to menu */
73
72
  menuProps?: MenuProps;
74
73
  /** Additional props applied to panel */
75
74
  drawerPanelContentProps?: DrawerPanelContentProps;
@@ -105,6 +104,8 @@ export interface ChatbotConversationHistoryNavProps extends DrawerProps {
105
104
  navTitleProps?: Partial<TitleProps>;
106
105
  /** Visually hidden text that gets announced by assistive technologies. Should be used to convey the result count when the search input value changes. */
107
106
  searchInputScreenReaderText?: string;
107
+ /** Additional props passed to MenuContent */
108
+ menuContentProps?: Omit<MenuContentProps, 'ref'>;
108
109
  }
109
110
  export declare const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversationHistoryNavProps>;
110
111
  export default ChatbotConversationHistoryNav;
@@ -12,28 +12,40 @@ var __rest = (this && this.__rest) || function (s, e) {
12
12
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  import { useRef, Fragment } from 'react';
14
14
  // Import PatternFly components
15
- import { Button, Drawer, DrawerPanelContent, DrawerContent, DrawerPanelBody, DrawerHead, DrawerActions, DrawerCloseButton, DrawerContentBody, SearchInput, List, ListItem, Title, Icon } from '@patternfly/react-core';
15
+ import { Button, Drawer, DrawerPanelContent, DrawerContent, DrawerPanelBody, DrawerHead, DrawerActions, DrawerCloseButton, DrawerContentBody, SearchInput, Title, Icon, MenuList, MenuGroup, MenuItem, Menu, MenuContent } from '@patternfly/react-core';
16
16
  import { OutlinedClockIcon, OutlinedCommentAltIcon, PenToSquareIcon } from '@patternfly/react-icons';
17
17
  import { ChatbotDisplayMode } from '../Chatbot/Chatbot';
18
18
  import ConversationHistoryDropdown from './ChatbotConversationHistoryDropdown';
19
19
  import LoadingState from './LoadingState';
20
20
  import HistoryEmptyState from './EmptyState';
21
21
  export const ChatbotConversationHistoryNav = (_a) => {
22
- var { onDrawerToggle, isDrawerOpen, setIsDrawerOpen, activeItemId, onSelectActiveItem, conversations, listTitleProps, listProps, newChatButtonText = 'New chat', drawerContent, onNewChat, newChatButtonProps, searchInputPlaceholder = 'Search previous conversations...', searchInputAriaLabel = 'Search previous conversations', searchInputProps, handleTextInputChange, displayMode, reverseButtonOrder = false, drawerActionsTestId = 'chatbot-nav-drawer-actions', drawerPanelContentProps, drawerContentProps, drawerContentBodyProps, drawerHeadProps, drawerActionsProps, drawerCloseButtonProps, drawerPanelBodyProps, isLoading, loadingState, errorState, emptyState, noResultsState, isCompact, title = 'Chat history', navTitleProps, navTitleIcon = _jsx(OutlinedClockIcon, {}), searchInputScreenReaderText } = _a, props = __rest(_a, ["onDrawerToggle", "isDrawerOpen", "setIsDrawerOpen", "activeItemId", "onSelectActiveItem", "conversations", "listTitleProps", "listProps", "newChatButtonText", "drawerContent", "onNewChat", "newChatButtonProps", "searchInputPlaceholder", "searchInputAriaLabel", "searchInputProps", "handleTextInputChange", "displayMode", "reverseButtonOrder", "drawerActionsTestId", "drawerPanelContentProps", "drawerContentProps", "drawerContentBodyProps", "drawerHeadProps", "drawerActionsProps", "drawerCloseButtonProps", "drawerPanelBodyProps", "isLoading", "loadingState", "errorState", "emptyState", "noResultsState", "isCompact", "title", "navTitleProps", "navTitleIcon", "searchInputScreenReaderText"]);
22
+ var { onDrawerToggle, isDrawerOpen, setIsDrawerOpen, activeItemId, onSelectActiveItem, conversations, menuListProps, newChatButtonText = 'New chat', drawerContent, onNewChat, newChatButtonProps, searchInputPlaceholder = 'Search previous conversations...', searchInputAriaLabel = 'Search previous conversations', searchInputProps, handleTextInputChange, displayMode, reverseButtonOrder = false, drawerActionsTestId = 'chatbot-nav-drawer-actions', drawerPanelContentProps, drawerContentProps, drawerContentBodyProps, drawerHeadProps, drawerActionsProps, drawerCloseButtonProps, drawerPanelBodyProps, isLoading, loadingState, errorState, emptyState, noResultsState, isCompact, title = 'Chat history', navTitleProps, navTitleIcon = _jsx(OutlinedClockIcon, {}), searchInputScreenReaderText, menuProps, menuGroupProps, menuContentProps } = _a, props = __rest(_a, ["onDrawerToggle", "isDrawerOpen", "setIsDrawerOpen", "activeItemId", "onSelectActiveItem", "conversations", "menuListProps", "newChatButtonText", "drawerContent", "onNewChat", "newChatButtonProps", "searchInputPlaceholder", "searchInputAriaLabel", "searchInputProps", "handleTextInputChange", "displayMode", "reverseButtonOrder", "drawerActionsTestId", "drawerPanelContentProps", "drawerContentProps", "drawerContentBodyProps", "drawerHeadProps", "drawerActionsProps", "drawerCloseButtonProps", "drawerPanelBodyProps", "isLoading", "loadingState", "errorState", "emptyState", "noResultsState", "isCompact", "title", "navTitleProps", "navTitleIcon", "searchInputScreenReaderText", "menuProps", "menuGroupProps", "menuContentProps"]);
23
23
  const drawerRef = useRef(null);
24
24
  const onExpand = () => {
25
25
  drawerRef.current && drawerRef.current.focus();
26
26
  };
27
+ const isConversation = (item) => item && typeof item === 'object' && 'id' in item && 'text' in item;
27
28
  const getNavItem = (conversation) => {
28
29
  var _a;
29
- return (_jsx(ListItem, Object.assign({ className: `pf-chatbot__conversation-list-item ${activeItemId && activeItemId === conversation.id ? 'pf-chatbot__conversation-list-item--active' : ''}` }, conversation.listItemProps, { children: _jsxs(_Fragment, { children: [_jsx(Button, Object.assign({ className: "pf-chatbot__conversation-history-item", variant: "link" }, conversation.additionalProps, (conversation.noIcon ? {} : { icon: (_a = conversation.icon) !== null && _a !== void 0 ? _a : _jsx(OutlinedCommentAltIcon, {}) }), { onClick: (event) => onSelectActiveItem === null || onSelectActiveItem === void 0 ? void 0 : onSelectActiveItem(event, conversation.id), children: conversation.text })), conversation.menuItems && (_jsx(ConversationHistoryDropdown, { menuClassName: conversation.menuClassName, onSelect: conversation.onSelect, menuItems: conversation.menuItems, label: conversation.label, id: conversation.dropdownId }))] }) }), conversation.id));
30
+ return (_jsx(MenuItem, Object.assign({ className: `pf-chatbot__menu-item ${activeItemId && activeItemId === conversation.id ? 'pf-chatbot__menu-item--active' : ''}`, itemId: conversation.id }, (conversation.noIcon ? {} : { icon: (_a = conversation.icon) !== null && _a !== void 0 ? _a : _jsx(OutlinedCommentAltIcon, {}) }), (conversation.menuItems
31
+ ? {
32
+ actions: (_jsx(ConversationHistoryDropdown, { menuClassName: conversation.menuClassName, onSelect: conversation.onSelect, menuItems: conversation.menuItems, label: conversation.label }))
33
+ }
34
+ : {}), conversation.additionalProps, { children: conversation.text })));
30
35
  };
31
36
  const buildConversations = () => {
32
37
  if (Array.isArray(conversations)) {
33
- return (_jsx(List, Object.assign({ className: "pf-chatbot__conversation-list", isPlain: true }, listProps, { children: conversations.map((conversation) => (_jsx(Fragment, { children: getNavItem(conversation) }, conversation.id))) })));
38
+ return (_jsx(MenuList, Object.assign({}, menuListProps, { children: conversations.map((conversation) => {
39
+ if (isConversation(conversation)) {
40
+ return _jsx(Fragment, { children: getNavItem(conversation) }, conversation.id);
41
+ }
42
+ else {
43
+ return conversation;
44
+ }
45
+ }) })));
34
46
  }
35
47
  else {
36
- return (_jsx("div", { children: Object.keys(conversations).map((navGroup) => (_jsxs("section", { children: [_jsx(Title, Object.assign({ headingLevel: "h4", className: "pf-chatbot__conversation-list-header" }, listTitleProps, { children: navGroup })), _jsx(List, Object.assign({ className: "pf-chatbot__conversation-list", isPlain: true }, listProps === null || listProps === void 0 ? void 0 : listProps[navGroup], { children: conversations[navGroup].map((conversation) => (_jsx(Fragment, { children: getNavItem(conversation) }, conversation.id))) }))] }, navGroup))) }));
48
+ return (_jsx(_Fragment, { children: Object.keys(conversations).map((navGroup) => (_jsx(MenuGroup, Object.assign({ className: "pf-chatbot__menu-item-header", label: navGroup, labelHeadingLevel: "h3" }, menuGroupProps === null || menuGroupProps === void 0 ? void 0 : menuGroupProps[navGroup], { children: _jsx(MenuList, Object.assign({}, menuListProps === null || menuListProps === void 0 ? void 0 : menuListProps[navGroup], { children: conversations[navGroup].map((conversation) => (_jsx(Fragment, { children: getNavItem(conversation) }, conversation.id))) })) }), navGroup))) }));
37
49
  }
38
50
  };
39
51
  // Menu Content
@@ -49,7 +61,7 @@ export const ChatbotConversationHistoryNav = (_a) => {
49
61
  if (noResultsState) {
50
62
  return _jsx(HistoryEmptyState, Object.assign({}, noResultsState));
51
63
  }
52
- return _jsx(_Fragment, { children: buildConversations() });
64
+ return (_jsx(Menu, Object.assign({ isPlain: true, onSelect: onSelectActiveItem, activeItemId: activeItemId }, menuProps, { children: _jsx(MenuContent, Object.assign({}, menuContentProps, { children: buildConversations() })) })));
53
65
  };
54
66
  const renderDrawerContent = () => (_jsx(_Fragment, { children: _jsx(DrawerPanelBody, Object.assign({}, drawerPanelBodyProps, { children: renderMenuContent() })) }));
55
67
  const renderPanelContent = () => {
@@ -193,26 +193,30 @@ describe('ChatbotConversationHistoryNav', () => {
193
193
  const iconElement = container.querySelector('.pf-chatbot__title-icon');
194
194
  expect(iconElement).toBeInTheDocument();
195
195
  });
196
- it('Passes listTitleProps to Title', () => {
197
- render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: { Today: initialConversations }, listTitleProps: { className: 'test' } }));
198
- expect(screen.getByRole('heading', { name: /Today/i })).toHaveClass('test');
199
- });
200
- it('Overrides list title heading level when titleProps.headingLevel is passed', () => {
201
- render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: { Today: initialConversations }, listTitleProps: { headingLevel: 'h2' } }));
202
- expect(screen.queryByRole('heading', { name: /Today/i, level: 4 })).not.toBeInTheDocument();
203
- expect(screen.getByRole('heading', { name: /Today/i, level: 2 })).toBeInTheDocument();
204
- });
205
- it('Passes listProps to List when conversations is an array', () => {
206
- render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: initialConversations, listProps: { className: 'test' } }));
207
- expect(screen.getByRole('list')).toHaveClass('test');
208
- });
209
- it('Passes listProps to List when conversations is an object', () => {
210
- render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: { Today: initialConversations }, listProps: { Today: { className: 'test' } } }));
211
- expect(screen.getByRole('list')).toHaveClass('test');
212
- });
213
- it('Passes listItemProps to ListItem', () => {
214
- render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: [{ id: '1', text: 'ChatBot documentation', listItemProps: { className: 'test' } }] }));
215
- expect(screen.getByRole('listitem')).toHaveClass('test');
196
+ it('Passes menuProps to Menu', () => {
197
+ var _a;
198
+ render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: initialConversations, menuProps: { className: 'test' } }));
199
+ expect((_a = screen.getByRole('menu').parentElement) === null || _a === void 0 ? void 0 : _a.parentElement).toHaveClass('test');
200
+ });
201
+ it('Passes menuContentProps to MenuContent', () => {
202
+ render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: initialConversations, menuContentProps: { className: 'test' } }));
203
+ expect(screen.getByRole('menu').parentElement).toHaveClass('test');
204
+ });
205
+ it('Passes menuListProps to MenuList when conversations is an array', () => {
206
+ render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: initialConversations, menuListProps: { className: 'test' } }));
207
+ expect(screen.getByRole('menu')).toHaveClass('test');
208
+ });
209
+ it('Passes menuListProps to MenuList when conversations is an object', () => {
210
+ render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: { Today: initialConversations }, menuListProps: { Today: { className: 'test' } } }));
211
+ expect(screen.getByRole('menu')).toHaveClass('test');
212
+ });
213
+ it('Passes menuGroupProps to MenuGroup when conversations is an object', () => {
214
+ render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: { Today: initialConversations }, menuGroupProps: { Today: { className: 'test' } } }));
215
+ expect(screen.getByRole('menu').parentElement).toHaveClass('test');
216
+ });
217
+ it('Passes additionalProps to MenuItem', () => {
218
+ render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: [{ id: '1', text: 'ChatBot documentation', additionalProps: { className: 'test' } }] }));
219
+ expect(screen.getByRole('menuitem')).toHaveClass('test');
216
220
  });
217
221
  it('should be able to spread search input props when searchInputProps is passed', () => {
218
222
  render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: initialConversations, handleTextInputChange: jest.fn(), searchInputProps: { value: 'I am a sample search' } }));
@@ -1,5 +1,5 @@
1
1
  import type { FunctionComponent } from 'react';
2
- import { ButtonProps, CardProps } from '@patternfly/react-core';
2
+ import { ButtonProps, CardBodyProps, CardFooterProps, CardProps, CardTitleProps, TruncateProps } from '@patternfly/react-core';
3
3
  export interface SourcesCardProps extends CardProps {
4
4
  /** Additional classes for the pagination navigation container. */
5
5
  className?: string;
@@ -13,6 +13,8 @@ export interface SourcesCardProps extends CardProps {
13
13
  sources: {
14
14
  /** Title of sources card */
15
15
  title?: string;
16
+ /** Subtitle of sources card */
17
+ subtitle?: string;
16
18
  /** Link to source */
17
19
  link: string;
18
20
  /** Body of sources card */
@@ -25,6 +27,10 @@ export interface SourcesCardProps extends CardProps {
25
27
  onClick?: React.MouseEventHandler<HTMLButtonElement>;
26
28
  /** Any additional props applied to the title of the Sources card */
27
29
  titleProps?: ButtonProps;
30
+ /** Custom footer applied to the Sources card */
31
+ footer?: React.ReactNode;
32
+ /** Additional props passed to Truncate component */
33
+ truncateProps?: TruncateProps;
28
34
  }[];
29
35
  /** Label for the English word "source" */
30
36
  sourceWord?: string;
@@ -44,6 +50,12 @@ export interface SourcesCardProps extends CardProps {
44
50
  showMoreWords?: string;
45
51
  /** Label for English words "show less" */
46
52
  showLessWords?: string;
53
+ /** Additional props passed to card title */
54
+ cardTitleProps?: CardTitleProps;
55
+ /** Additional props passed to card body */
56
+ cardBodyProps?: CardBodyProps;
57
+ /** Additional props passed to card footer */
58
+ cardFooterProps?: CardFooterProps;
47
59
  }
48
60
  declare const SourcesCard: FunctionComponent<SourcesCardProps>;
49
61
  export default SourcesCard;
@@ -16,7 +16,7 @@ import { Button, ButtonVariant, Card, CardBody, CardFooter, CardTitle, Expandabl
16
16
  import { ExternalLinkSquareAltIcon } from '@patternfly/react-icons';
17
17
  const SourcesCard = (_a) => {
18
18
  var _b;
19
- var { className, isDisabled, paginationAriaLabel = 'Pagination', sources, sourceWord = 'source', sourceWordPlural = 'sources', toNextPageAriaLabel = 'Go to next page', toPreviousPageAriaLabel = 'Go to previous page', onNextClick, onPreviousClick, onSetPage, showMoreWords = 'show more', showLessWords = 'show less', isCompact } = _a, props = __rest(_a, ["className", "isDisabled", "paginationAriaLabel", "sources", "sourceWord", "sourceWordPlural", "toNextPageAriaLabel", "toPreviousPageAriaLabel", "onNextClick", "onPreviousClick", "onSetPage", "showMoreWords", "showLessWords", "isCompact"]);
19
+ var { className, isDisabled, paginationAriaLabel = 'Pagination', sources, sourceWord = 'source', sourceWordPlural = 'sources', toNextPageAriaLabel = 'Go to next page', toPreviousPageAriaLabel = 'Go to previous page', onNextClick, onPreviousClick, onSetPage, showMoreWords = 'show more', showLessWords = 'show less', isCompact, cardTitleProps, cardBodyProps, cardFooterProps } = _a, props = __rest(_a, ["className", "isDisabled", "paginationAriaLabel", "sources", "sourceWord", "sourceWordPlural", "toNextPageAriaLabel", "toPreviousPageAriaLabel", "onNextClick", "onPreviousClick", "onSetPage", "showMoreWords", "showLessWords", "isCompact", "cardTitleProps", "cardBodyProps", "cardFooterProps"]);
20
20
  const [page, setPage] = useState(1);
21
21
  const [isExpanded, setIsExpanded] = useState(false);
22
22
  const onToggle = (_event, isExpanded) => {
@@ -26,15 +26,15 @@ const SourcesCard = (_a) => {
26
26
  setPage(newPage);
27
27
  onSetPage && onSetPage(_evt, newPage);
28
28
  };
29
- const renderTitle = (title) => {
29
+ const renderTitle = (title, truncateProps) => {
30
30
  if (title) {
31
- return _jsx(Truncate, { content: title });
31
+ return _jsx(Truncate, Object.assign({ content: title }, truncateProps));
32
32
  }
33
33
  return `Source ${page}`;
34
34
  };
35
- return (_jsxs("div", { className: "pf-chatbot__source", children: [_jsx("span", { children: pluralize(sources.length, sourceWord, sourceWordPlural) }), _jsxs(Card, Object.assign({ isCompact: isCompact, className: "pf-chatbot__sources-card" }, props, { children: [_jsx(CardTitle, { className: "pf-chatbot__sources-card-title", children: _jsx(Button, Object.assign({ component: "a", variant: ButtonVariant.link, href: sources[page - 1].link, icon: sources[page - 1].isExternal ? _jsx(ExternalLinkSquareAltIcon, {}) : undefined, iconPosition: "end", isInline: true, rel: sources[page - 1].isExternal ? 'noreferrer' : undefined, target: sources[page - 1].isExternal ? '_blank' : undefined, onClick: (_b = sources[page - 1].onClick) !== null && _b !== void 0 ? _b : undefined }, sources[page - 1].titleProps, { children: renderTitle(sources[page - 1].title) })) }), sources[page - 1].body && (_jsx(CardBody, { className: `pf-chatbot__sources-card-body`, children: sources[page - 1].hasShowMore ? (
35
+ return (_jsxs("div", { className: "pf-chatbot__source", children: [_jsx("span", { children: pluralize(sources.length, sourceWord, sourceWordPlural) }), _jsxs(Card, Object.assign({ isCompact: isCompact, className: "pf-chatbot__sources-card" }, props, { children: [_jsx(CardTitle, Object.assign({ className: "pf-chatbot__sources-card-title" }, cardTitleProps, { children: _jsxs("div", { className: "pf-chatbot__sources-card-title-container", children: [_jsx(Button, Object.assign({ component: "a", variant: ButtonVariant.link, href: sources[page - 1].link, icon: sources[page - 1].isExternal ? _jsx(ExternalLinkSquareAltIcon, {}) : undefined, iconPosition: "end", isInline: true, rel: sources[page - 1].isExternal ? 'noreferrer' : undefined, target: sources[page - 1].isExternal ? '_blank' : undefined, onClick: (_b = sources[page - 1].onClick) !== null && _b !== void 0 ? _b : undefined }, sources[page - 1].titleProps, { children: renderTitle(sources[page - 1].title, sources[page - 1].truncateProps) })), sources[page - 1].subtitle && (_jsx("span", { className: "pf-chatbot__sources-card-subtitle", children: sources[page - 1].subtitle }))] }) })), sources[page - 1].body && (_jsx(CardBody, Object.assign({ className: `pf-chatbot__sources-card-body ${sources[page - 1].footer ? 'pf-chatbot__compact-sources-card-body' : undefined}` }, cardBodyProps, { children: sources[page - 1].hasShowMore ? (
36
36
  // prevents extra VO announcements of button text - parent Message has aria-live
37
- _jsx("div", { "aria-live": "off", children: _jsx(ExpandableSection, { variant: ExpandableSectionVariant.truncate, toggleText: isExpanded ? showLessWords : showMoreWords, onToggle: onToggle, isExpanded: isExpanded, truncateMaxLines: 2, children: sources[page - 1].body }) })) : (_jsx("div", { className: "pf-chatbot__sources-card-body-text", children: sources[page - 1].body })) })), sources.length > 1 && (_jsx(CardFooter, { className: "pf-chatbot__sources-card-footer-container", children: _jsx("div", { className: "pf-chatbot__sources-card-footer", children: _jsxs("nav", { className: `pf-chatbot__sources-card-footer-buttons ${className}`, "aria-label": paginationAriaLabel, children: [_jsx(Button, { variant: ButtonVariant.plain, isDisabled: isDisabled || page === 1, "data-action": "previous", onClick: (event) => {
37
+ _jsx("div", { "aria-live": "off", children: _jsx(ExpandableSection, { variant: ExpandableSectionVariant.truncate, toggleText: isExpanded ? showLessWords : showMoreWords, onToggle: onToggle, isExpanded: isExpanded, truncateMaxLines: 2, children: sources[page - 1].body }) })) : (_jsx("div", { className: "pf-chatbot__sources-card-body-text", children: sources[page - 1].body })) }))), sources[page - 1].footer ? (_jsx(CardFooter, Object.assign({ className: "pf-chatbot__sources-card-footer" }, cardFooterProps, { children: sources[page - 1].footer }))) : (sources.length > 1 && (_jsx(CardFooter, Object.assign({ className: "pf-chatbot__sources-card-footer-container" }, cardFooterProps, { children: _jsx("div", { className: "pf-chatbot__sources-card-footer", children: _jsxs("nav", { className: `pf-chatbot__sources-card-footer-buttons ${className}`, "aria-label": paginationAriaLabel, children: [_jsx(Button, { variant: ButtonVariant.plain, isDisabled: isDisabled || page === 1, "data-action": "previous", onClick: (event) => {
38
38
  const newPage = page >= 1 ? page - 1 : 1;
39
39
  onPreviousClick && onPreviousClick(event, newPage);
40
40
  handleNewPage(event, newPage);
@@ -42,6 +42,6 @@ const SourcesCard = (_a) => {
42
42
  const newPage = page + 1 <= sources.length ? page + 1 : sources.length;
43
43
  onNextClick && onNextClick(event, newPage);
44
44
  handleNewPage(event, newPage);
45
- }, children: _jsx(Icon, { isInline: true, iconSize: "lg", children: _jsx("svg", { className: "pf-v6-svg", viewBox: "0 0 180 500", fill: "currentColor", "aria-hidden": "true", role: "img", width: "1em", height: "1em", children: _jsx("path", { d: "M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z" }) }) }) })] }) }) }))] }))] }));
45
+ }, children: _jsx(Icon, { isInline: true, iconSize: "lg", children: _jsx("svg", { className: "pf-v6-svg", viewBox: "0 0 180 500", fill: "currentColor", "aria-hidden": "true", role: "img", width: "1em", height: "1em", children: _jsx("path", { d: "M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z" }) }) }) })] }) }) }))))] }))] }));
46
46
  };
47
47
  export default SourcesCard;
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { jsx as _jsx } from "react/jsx-runtime";
10
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
11
11
  import { render, screen } from '@testing-library/react';
12
12
  import userEvent from '@testing-library/user-event';
13
13
  import '@testing-library/jest-dom';
@@ -179,4 +179,53 @@ describe('SourcesCard', () => {
179
179
  render(_jsx(SourcesCard, { sources: [{ title: 'How to make an apple pie', link: '', titleProps: { className: 'test' } }] }));
180
180
  expect(screen.getByRole('link', { name: /How to make an apple pie/i })).toHaveClass('test');
181
181
  });
182
+ it('should apply cardTitleProps appropriately', () => {
183
+ render(_jsx(SourcesCard, { cardTitleProps: { 'data-testid': 'card-title', className: 'test' }, sources: [{ title: 'How to make an apple pie', link: '' }] }));
184
+ expect(screen.getByTestId('card-title')).toHaveClass('test');
185
+ });
186
+ it('should apply cardBodyProps appropriately', () => {
187
+ render(_jsx(SourcesCard, { cardBodyProps: { 'data-testid': 'card-body', body: 'To make an apple pie, you must first...', className: 'test' }, sources: [{ title: 'How to make an apple pie', link: '', body: 'To make an apple pie, you must first...' }] }));
188
+ expect(screen.getByTestId('card-body')).toHaveClass('test');
189
+ });
190
+ it('should apply cardFooterProps appropriately', () => {
191
+ render(_jsx(SourcesCard, { cardFooterProps: { 'data-testid': 'card-footer', className: 'test' }, sources: [
192
+ { title: 'How to make an apple pie', link: '' },
193
+ { title: 'How to make cookies', link: '' }
194
+ ] }));
195
+ expect(screen.getByTestId('card-footer')).toHaveClass('test');
196
+ });
197
+ it('should apply truncateProps appropriately', () => {
198
+ render(_jsx(SourcesCard, { sources: [
199
+ {
200
+ title: 'How to make an apple pie',
201
+ link: '',
202
+ truncateProps: { 'data-testid': 'card-truncate', className: 'test' }
203
+ }
204
+ ] }));
205
+ expect(screen.getByTestId('card-truncate')).toHaveClass('test');
206
+ });
207
+ it('should apply custom footer appropriately when there is one source', () => {
208
+ render(_jsx(SourcesCard, { sources: [{ title: 'How to make an apple pie', link: '', footer: _jsx(_Fragment, { children: "I am a custom footer" }) }] }));
209
+ expect(screen.getByText('I am a custom footer'));
210
+ expect(screen.queryByText('1/1')).toBeFalsy();
211
+ });
212
+ it('should apply custom footer appropriately when are multiple sources', () => {
213
+ render(_jsx(SourcesCard, { sources: [
214
+ { title: 'How to make an apple pie', link: '', footer: _jsx(_Fragment, { children: "I am a custom footer" }) },
215
+ { title: 'How to bake bread', link: '' }
216
+ ] }));
217
+ expect(screen.getByText('I am a custom footer'));
218
+ // does not show navigation bar
219
+ expect(screen.queryByText('1/2')).toBeFalsy();
220
+ });
221
+ it('should apply footer props to custom footer appropriately', () => {
222
+ render(_jsx(SourcesCard, { cardFooterProps: { 'data-testid': 'card-footer', className: 'test' }, sources: [{ title: 'How to make an apple pie', link: '', footer: _jsx(_Fragment, { children: "I am a custom footer" }) }] }));
223
+ expect(screen.getByText('I am a custom footer'));
224
+ expect(screen.getByTestId('card-footer')).toHaveClass('test');
225
+ });
226
+ it('should apply subtitle appropriately', () => {
227
+ render(_jsx(SourcesCard, { sources: [{ title: 'How to make an apple pie', link: '', subtitle: 'You must first create the universe' }] }));
228
+ expect(screen.getByText('How to make an apple pie'));
229
+ expect(screen.getByText('You must first create the universe'));
230
+ });
182
231
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/chatbot",
3
- "version": "6.4.0-prerelease.14",
3
+ "version": "6.4.0-prerelease.16",
4
4
  "description": "This library provides React components based on PatternFly 6 that can be used to build chatbots.",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -53,7 +53,7 @@
53
53
  "react-dom": "^18 || ^19"
54
54
  },
55
55
  "devDependencies": {
56
- "@patternfly/documentation-framework": "6.16.0",
56
+ "@patternfly/documentation-framework": "6.19.0",
57
57
  "@patternfly/patternfly": "^6.1.0",
58
58
  "@patternfly/patternfly-a11y": "^5.0.0",
59
59
  "@types/dom-speech-recognition": "^0.0.4",
@@ -1,6 +1,8 @@
1
1
  import { FunctionComponent, MouseEvent as ReactMouseEvent, KeyboardEvent as ReactKeyboardEvent } from 'react';
2
2
  import Message from '@patternfly/chatbot/dist/dynamic/Message';
3
3
  import patternflyAvatar from './patternfly_avatar.jpg';
4
+ import { Button, Flex, FlexItem, Label, Popover } from '@patternfly/react-core';
5
+ import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons';
4
6
 
5
7
  export const MessageWithSourcesExample: FunctionComponent = () => {
6
8
  const onSetPage = (_event: ReactMouseEvent | ReactKeyboardEvent | MouseEvent, newPage: number) => {
@@ -8,8 +10,76 @@ export const MessageWithSourcesExample: FunctionComponent = () => {
8
10
  console.log(`Page changed to ${newPage}`);
9
11
  };
10
12
 
13
+ const date = new Date();
14
+
15
+ const datePart = date.toLocaleDateString('en', {
16
+ year: 'numeric',
17
+ month: 'short',
18
+ day: 'numeric'
19
+ });
20
+
21
+ const timePart = date.toLocaleTimeString('en', {
22
+ hour: '2-digit',
23
+ minute: '2-digit',
24
+ hour12: true
25
+ });
26
+
27
+ const formattedDate = `${datePart}, ${timePart}`;
28
+
11
29
  return (
12
30
  <>
31
+ <Message
32
+ name="Bot"
33
+ role="bot"
34
+ avatar={patternflyAvatar}
35
+ content="This example has a custom subtitle and footer with no pagination"
36
+ sources={{
37
+ sources: [
38
+ {
39
+ title: 'Getting started with Red Hat OpenShift',
40
+ subtitle: 'Red Hat knowledge base',
41
+ link: '#',
42
+ body: 'Red Hat OpenShift on IBM Cloud is a managed offering to create your own cluster of compute hosts where you can deploy and manage containerized apps on IBM Cloud ...',
43
+ isExternal: true,
44
+ footer: (
45
+ <Flex className="pf-chatbot__sources-card-subtle" gap={{ default: 'gapXs' }}>
46
+ <FlexItem alignSelf={{ default: 'alignSelfStretch' }}>
47
+ <Flex justifyContent={{ default: 'justifyContentSpaceBetween' }}>
48
+ <FlexItem>
49
+ <Label color="green">Confidence 93%</Label>
50
+ </FlexItem>
51
+ <FlexItem>
52
+ <Popover
53
+ headerContent={
54
+ <Flex gap={{ default: 'gapXs' }}>
55
+ <FlexItem>
56
+ <OutlinedQuestionCircleIcon />
57
+ </FlexItem>
58
+ <FlexItem>Why this confidence score?</FlexItem>
59
+ </Flex>
60
+ }
61
+ bodyContent={
62
+ <>
63
+ A high confidence score indicates a strong match. The system found significant overlap in
64
+ key data points, including text content, names, dates, and organizational details, with a
65
+ high degree of certainty. This match is highly reliable.
66
+ </>
67
+ }
68
+ >
69
+ <Button variant="link" icon={<OutlinedQuestionCircleIcon />}>
70
+ Learn about this score
71
+ </Button>
72
+ </Popover>
73
+ </FlexItem>
74
+ </Flex>
75
+ </FlexItem>
76
+ <FlexItem>{`Last updated: ${formattedDate}`}</FlexItem>
77
+ </Flex>
78
+ )
79
+ }
80
+ ]
81
+ }}
82
+ />
13
83
  <Message
14
84
  name="Bot"
15
85
  role="bot"
@@ -34,7 +34,7 @@ import Message from '@patternfly/chatbot/dist/dynamic/Message';
34
34
  import MessageDivider from '@patternfly/chatbot/dist/dynamic/MessageDivider';
35
35
  import { rehypeCodeBlockToggle } from '@patternfly/chatbot/dist/esm/Message/Plugins/rehypeCodeBlockToggle';
36
36
  import SourcesCard from '@patternfly/chatbot/dist/dynamic/SourcesCard';
37
- import { ArrowCircleDownIcon, ArrowRightIcon, CheckCircleIcon, CubeIcon, CubesIcon, DownloadIcon, InfoCircleIcon, RedoIcon, RobotIcon } from '@patternfly/react-icons';
37
+ import { ArrowCircleDownIcon, ArrowRightIcon, CheckCircleIcon, CubeIcon, CubesIcon, DownloadIcon, InfoCircleIcon, OutlinedQuestionCircleIcon, RedoIcon, RobotIcon } from '@patternfly/react-icons';
38
38
  import patternflyAvatar from './patternfly_avatar.jpg';
39
39
  import AttachmentEdit from '@patternfly/chatbot/dist/dynamic/AttachmentEdit';
40
40
  import FileDetails from '@patternfly/chatbot/dist/dynamic/FileDetails';
@@ -2,7 +2,7 @@
2
2
  module.exports = {
3
3
  sideNavItems: [{ section: 'PatternFly-AI' }],
4
4
  topNavItems: [],
5
- hasDarkThemeSwitcher: true,
5
+ hasThemeSwitcher: true,
6
6
  hasRTLSwitcher: true,
7
7
  port: 8006
8
8
  };
@@ -14,13 +14,13 @@ describe('ChatbotConversationHistoryDropdown', () => {
14
14
 
15
15
  it('should render the dropdown', () => {
16
16
  render(<ChatbotConversationHistoryDropdown menuItems={menuItems} menuClassName="custom-class" />);
17
- expect(screen.queryByRole('button', { name: /Conversation options/i })).toBeInTheDocument();
17
+ expect(screen.queryByRole('menuitem', { name: /Conversation options/i })).toBeInTheDocument();
18
18
  });
19
19
 
20
20
  it('should display the dropdown menuItems', () => {
21
21
  render(<ChatbotConversationHistoryDropdown menuItems={menuItems} />);
22
22
 
23
- const toggle = screen.queryByRole('button', { name: /Conversation options/i })!;
23
+ const toggle = screen.queryByRole('menuitem', { name: /Conversation options/i })!;
24
24
 
25
25
  expect(toggle).toBeInTheDocument();
26
26
  fireEvent.click(toggle);
@@ -33,7 +33,7 @@ describe('ChatbotConversationHistoryDropdown', () => {
33
33
 
34
34
  it('should invoke onSelect callback when menuitem is clicked', () => {
35
35
  render(<ChatbotConversationHistoryDropdown menuItems={menuItems} onSelect={onSelect} />);
36
- const toggle = screen.queryByRole('button', { name: /Conversation options/i })!;
36
+ const toggle = screen.queryByRole('menuitem', { name: /Conversation options/i })!;
37
37
  fireEvent.click(toggle);
38
38
  fireEvent.click(screen.getByText('Rename'));
39
39
 
@@ -42,7 +42,7 @@ describe('ChatbotConversationHistoryDropdown', () => {
42
42
 
43
43
  it('should toggle the dropdown when menuitem is clicked', () => {
44
44
  render(<ChatbotConversationHistoryDropdown menuItems={menuItems} onSelect={onSelect} />);
45
- const toggle = screen.queryByRole('button', { name: /Conversation options/i })!;
45
+ const toggle = screen.queryByRole('menuitem', { name: /Conversation options/i })!;
46
46
  fireEvent.click(toggle);
47
47
  fireEvent.click(screen.getByText('Delete'));
48
48
 
@@ -53,7 +53,7 @@ describe('ChatbotConversationHistoryDropdown', () => {
53
53
 
54
54
  it('should close the dropdown when user clicks outside', () => {
55
55
  render(<ChatbotConversationHistoryDropdown menuItems={menuItems} onSelect={onSelect} />);
56
- const toggle = screen.queryByRole('button', { name: /Conversation options/i })!;
56
+ const toggle = screen.queryByRole('menuitem', { name: /Conversation options/i })!;
57
57
  fireEvent.click(toggle);
58
58
 
59
59
  expect(screen.queryByText('Delete')).toBeInTheDocument();
@@ -64,7 +64,7 @@ describe('ChatbotConversationHistoryDropdown', () => {
64
64
 
65
65
  it('should show the tooltip when the user hovers over the toggle button', async () => {
66
66
  render(<ChatbotConversationHistoryDropdown menuItems={menuItems} label="Actions dropdown" />);
67
- const toggle = screen.queryByRole('button', { name: /Actions dropdown/i })!;
67
+ const toggle = screen.queryByRole('menuitem', { name: /Actions dropdown/i })!;
68
68
 
69
69
  fireEvent(
70
70
  toggle,
@@ -48,6 +48,7 @@ export const ChatbotConversationHistoryDropdown: FunctionComponent<ChatbotConver
48
48
  isExpanded={isOpen}
49
49
  onClick={() => setIsOpen(!isOpen)}
50
50
  id={id}
51
+ role="menuitem"
51
52
  >
52
53
  <EllipsisIcon />
53
54
  </MenuToggle>