@patternfly/chatbot 2.2.1 → 6.3.0-prerelease.1
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.
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +4 -0
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +7 -1
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +23 -0
- package/dist/cjs/Message/Message.d.ts +17 -1
- package/dist/cjs/Message/Message.js +53 -34
- package/dist/cjs/Message/Message.test.js +52 -0
- package/dist/cjs/Message/MessageInput.d.ts +18 -0
- package/dist/cjs/Message/MessageInput.js +34 -0
- package/dist/cjs/MessageBar/MicrophoneButton.js +1 -1
- package/dist/cjs/MessageBox/MessageBox.js +5 -5
- package/dist/cjs/SourcesCard/SourcesCard.d.ts +7 -1
- package/dist/cjs/SourcesCard/SourcesCard.js +16 -10
- package/dist/cjs/SourcesCard/SourcesCard.test.js +25 -15
- package/dist/cjs/tracking/console_tracking_provider.d.ts +4 -5
- package/dist/cjs/tracking/console_tracking_provider.js +22 -15
- package/dist/cjs/tracking/posthog_tracking_provider.d.ts +2 -2
- package/dist/cjs/tracking/posthog_tracking_provider.js +21 -12
- package/dist/cjs/tracking/segment_tracking_provider.d.ts +2 -2
- package/dist/cjs/tracking/segment_tracking_provider.js +21 -12
- package/dist/cjs/tracking/trackingProviderProxy.d.ts +1 -1
- package/dist/cjs/tracking/trackingProviderProxy.js +2 -2
- package/dist/cjs/tracking/tracking_api.d.ts +1 -1
- package/dist/cjs/tracking/tracking_registry.js +46 -12
- package/dist/cjs/tracking/tracking_spi.d.ts +15 -5
- package/dist/cjs/tracking/tracking_spi.js +9 -0
- package/dist/cjs/tracking/umami_tracking_provider.d.ts +6 -2
- package/dist/cjs/tracking/umami_tracking_provider.js +66 -22
- package/dist/css/main.css +7 -7
- package/dist/css/main.css.map +1 -1
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +4 -0
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +7 -1
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +23 -0
- package/dist/esm/Message/Message.d.ts +17 -1
- package/dist/esm/Message/Message.js +53 -34
- package/dist/esm/Message/Message.test.js +52 -0
- package/dist/esm/Message/MessageInput.d.ts +18 -0
- package/dist/esm/Message/MessageInput.js +29 -0
- package/dist/esm/MessageBar/MicrophoneButton.js +1 -1
- package/dist/esm/MessageBox/MessageBox.js +5 -5
- package/dist/esm/SourcesCard/SourcesCard.d.ts +7 -1
- package/dist/esm/SourcesCard/SourcesCard.js +17 -11
- package/dist/esm/SourcesCard/SourcesCard.test.js +25 -15
- package/dist/esm/tracking/console_tracking_provider.d.ts +4 -5
- package/dist/esm/tracking/console_tracking_provider.js +22 -15
- package/dist/esm/tracking/posthog_tracking_provider.d.ts +2 -2
- package/dist/esm/tracking/posthog_tracking_provider.js +21 -12
- package/dist/esm/tracking/segment_tracking_provider.d.ts +2 -2
- package/dist/esm/tracking/segment_tracking_provider.js +21 -12
- package/dist/esm/tracking/trackingProviderProxy.d.ts +1 -1
- package/dist/esm/tracking/trackingProviderProxy.js +2 -2
- package/dist/esm/tracking/tracking_api.d.ts +1 -1
- package/dist/esm/tracking/tracking_registry.js +46 -12
- package/dist/esm/tracking/tracking_spi.d.ts +15 -5
- package/dist/esm/tracking/tracking_spi.js +8 -1
- package/dist/esm/tracking/umami_tracking_provider.d.ts +6 -2
- package/dist/esm/tracking/umami_tracking_provider.js +66 -22
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/patternfly-docs/content/extensions/chatbot/examples/Analytics/Analytics.md +18 -14
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/BotMessage.tsx +74 -104
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/FileDetailsLabel.tsx +48 -37
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithQuickResponses.tsx +10 -0
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithSources.tsx +51 -14
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +3 -1
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx +80 -104
- package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawer.tsx +35 -2
- package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerResizable.tsx +13 -2
- package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +1 -1
- package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.tsx +6 -3
- package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotAttachment.tsx +2 -0
- package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotAttachmentMenu.tsx +2 -0
- package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotInDrawer.tsx +2 -0
- package/patternfly-docs/content/extensions/chatbot/examples/demos/EmbeddedChatbot.tsx +2 -0
- package/patternfly-docs/content/extensions/chatbot/examples/demos/EmbeddedComparisonChatbot.tsx +62 -57
- package/patternfly-docs/content/extensions/chatbot/examples/demos/Feedback.tsx +2 -0
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +53 -0
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +14 -0
- package/src/Message/Message.scss +4 -0
- package/src/Message/Message.test.tsx +62 -0
- package/src/Message/Message.tsx +111 -53
- package/src/Message/MessageInput.tsx +59 -0
- package/src/MessageBar/MicrophoneButton.tsx +1 -1
- package/src/MessageBox/MessageBox.tsx +5 -5
- package/src/SourcesCard/SourcesCard.scss +3 -7
- package/src/SourcesCard/SourcesCard.test.tsx +30 -22
- package/src/SourcesCard/SourcesCard.tsx +54 -12
- package/src/tracking/console_tracking_provider.ts +21 -17
- package/src/tracking/posthog_tracking_provider.ts +20 -13
- package/src/tracking/segment_tracking_provider.ts +20 -13
- package/src/tracking/trackingProviderProxy.ts +2 -2
- package/src/tracking/tracking_api.ts +1 -1
- package/src/tracking/tracking_registry.ts +46 -13
- package/src/tracking/tracking_spi.ts +18 -7
- package/src/tracking/umami_tracking_provider.ts +76 -20
- package/src/SourcesCard/__snapshots__/SourcesCard.test.tsx.snap +0 -34
@@ -13,6 +13,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
13
13
|
import { ChatbotDisplayMode } from '../Chatbot/Chatbot';
|
14
14
|
import ChatbotConversationHistoryNav from './ChatbotConversationHistoryNav';
|
15
15
|
import { EmptyStateStatus, Spinner } from '@patternfly/react-core';
|
16
|
+
import { OutlinedCommentsIcon, SearchIcon } from '@patternfly/react-icons';
|
16
17
|
const ERROR = {
|
17
18
|
bodyText: (React.createElement(React.Fragment, null,
|
18
19
|
"To try again, check your connection and reload this page. If the issue persists,",
|
@@ -26,6 +27,16 @@ const ERROR = {
|
|
26
27
|
status: EmptyStateStatus.danger,
|
27
28
|
onClick: () => alert('Clicked Reload')
|
28
29
|
};
|
30
|
+
const NO_RESULTS = {
|
31
|
+
bodyText: 'Adjust your search query and try again. Check your spelling or try a more general term.',
|
32
|
+
titleText: 'No results found',
|
33
|
+
icon: SearchIcon
|
34
|
+
};
|
35
|
+
const EMPTY_STATE = {
|
36
|
+
bodyText: 'Access timely assistance by starting a conversation with an AI model.',
|
37
|
+
titleText: 'Start a new chat',
|
38
|
+
icon: OutlinedCommentsIcon
|
39
|
+
};
|
29
40
|
const ERROR_WITHOUT_BUTTON = {
|
30
41
|
bodyText: (React.createElement(React.Fragment, null,
|
31
42
|
"To try again, check your connection and reload this page. If the issue persists,",
|
@@ -157,4 +168,16 @@ describe('ChatbotConversationHistoryNav', () => {
|
|
157
168
|
render(React.createElement(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), reverseButtonOrder: false, handleTextInputChange: jest.fn(), conversations: initialConversations, isLoading: true, errorState: ERROR }));
|
158
169
|
expect(screen.getByRole('dialog', { name: /Loading/i })).toBeTruthy();
|
159
170
|
});
|
171
|
+
it('should accept emptyState', () => {
|
172
|
+
render(React.createElement(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), reverseButtonOrder: false, handleTextInputChange: jest.fn(), conversations: initialConversations, emptyState: EMPTY_STATE }));
|
173
|
+
expect(screen.getByRole('dialog', {
|
174
|
+
name: /Start a new chat Access timely assistance by starting a conversation with an AI model./i
|
175
|
+
})).toBeTruthy();
|
176
|
+
});
|
177
|
+
it('should accept no results state', () => {
|
178
|
+
render(React.createElement(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), reverseButtonOrder: false, handleTextInputChange: jest.fn(), conversations: initialConversations, noResultsState: NO_RESULTS }));
|
179
|
+
expect(screen.getByRole('dialog', {
|
180
|
+
name: /No results found Adjust your search query and try again. Check your spelling or try a more general term./i
|
181
|
+
})).toBeTruthy();
|
182
|
+
});
|
160
183
|
});
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import React, { ReactNode } from 'react';
|
2
|
-
import { AlertProps, AvatarProps, LabelGroupProps } from '@patternfly/react-core';
|
2
|
+
import { AlertProps, AvatarProps, ButtonProps, FormProps, LabelGroupProps } from '@patternfly/react-core';
|
3
3
|
import { ActionProps } from '../ResponseActions/ResponseActions';
|
4
4
|
import { SourcesCardProps } from '../SourcesCard';
|
5
5
|
import { QuickStart, QuickstartAction } from './QuickStarts/types';
|
@@ -104,6 +104,22 @@ export interface MessageProps extends Omit<React.HTMLProps<HTMLDivElement>, 'rol
|
|
104
104
|
openLinkInNewTab?: boolean;
|
105
105
|
/** Optional inline error message that can be displayed in the message */
|
106
106
|
error?: AlertProps;
|
107
|
+
/** Props for links */
|
108
|
+
linkProps?: ButtonProps;
|
109
|
+
/** Whether message is in edit mode */
|
110
|
+
isEditable?: boolean;
|
111
|
+
/** Placeholder for edit input */
|
112
|
+
editPlaceholder?: string;
|
113
|
+
/** Label for the English word "Update" used in edit mode. */
|
114
|
+
updateWord?: string;
|
115
|
+
/** Label for the English word "Cancel" used in edit mode. */
|
116
|
+
cancelWord?: string;
|
117
|
+
/** Callback function for when edit mode update button is clicked */
|
118
|
+
onEditUpdate?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
119
|
+
/** Callback functionf or when edit cancel update button is clicked */
|
120
|
+
onEditCancel?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
121
|
+
/** Props for edit form */
|
122
|
+
editFormProps?: FormProps;
|
107
123
|
}
|
108
124
|
export declare const MessageBase: React.FunctionComponent<MessageProps>;
|
109
125
|
declare const Message: React.ForwardRefExoticComponent<Omit<MessageProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
@@ -41,8 +41,13 @@ import rehypeExternalLinks from 'rehype-external-links';
|
|
41
41
|
import rehypeSanitize from 'rehype-sanitize';
|
42
42
|
import LinkMessage from './LinkMessage/LinkMessage';
|
43
43
|
import ErrorMessage from './ErrorMessage/ErrorMessage';
|
44
|
+
import MessageInput from './MessageInput';
|
44
45
|
export const MessageBase = (_a) => {
|
45
|
-
var { role, content, extraContent, name, avatar, timestamp, isLoading, actions, sources, botWord = 'AI', loadingWord = 'Loading message', codeBlockProps, quickResponses, quickResponseContainerProps = { numLabels: 5 }, attachments, hasRoundAvatar = true, avatarProps, quickStarts, userFeedbackForm, userFeedbackComplete, isLiveRegion = true, innerRef, tableProps, openLinkInNewTab = true, additionalRehypePlugins = [], error } = _a, props = __rest(_a, ["role", "content", "extraContent", "name", "avatar", "timestamp", "isLoading", "actions", "sources", "botWord", "loadingWord", "codeBlockProps", "quickResponses", "quickResponseContainerProps", "attachments", "hasRoundAvatar", "avatarProps", "quickStarts", "userFeedbackForm", "userFeedbackComplete", "isLiveRegion", "innerRef", "tableProps", "openLinkInNewTab", "additionalRehypePlugins", "error"]);
|
46
|
+
var { role, content, extraContent, name, avatar, timestamp, isLoading, actions, sources, botWord = 'AI', loadingWord = 'Loading message', codeBlockProps, quickResponses, quickResponseContainerProps = { numLabels: 5 }, attachments, hasRoundAvatar = true, avatarProps, quickStarts, userFeedbackForm, userFeedbackComplete, isLiveRegion = true, innerRef, tableProps, openLinkInNewTab = true, additionalRehypePlugins = [], linkProps, error, isEditable, editPlaceholder = 'Edit prompt message...', updateWord = 'Update', cancelWord = 'Cancel', onEditUpdate, onEditCancel, editFormProps } = _a, props = __rest(_a, ["role", "content", "extraContent", "name", "avatar", "timestamp", "isLoading", "actions", "sources", "botWord", "loadingWord", "codeBlockProps", "quickResponses", "quickResponseContainerProps", "attachments", "hasRoundAvatar", "avatarProps", "quickStarts", "userFeedbackForm", "userFeedbackComplete", "isLiveRegion", "innerRef", "tableProps", "openLinkInNewTab", "additionalRehypePlugins", "linkProps", "error", "isEditable", "editPlaceholder", "updateWord", "cancelWord", "onEditUpdate", "onEditCancel", "editFormProps"]);
|
47
|
+
const [messageText, setMessageText] = React.useState(content);
|
48
|
+
React.useEffect(() => {
|
49
|
+
setMessageText(content);
|
50
|
+
}, [content]);
|
46
51
|
const { beforeMainContent, afterMainContent, endContent } = extraContent || {};
|
47
52
|
let rehypePlugins = [rehypeUnwrapImages];
|
48
53
|
if (openLinkInNewTab) {
|
@@ -60,6 +65,51 @@ export const MessageBase = (_a) => {
|
|
60
65
|
// Keep timestamps consistent between Timestamp component and aria-label
|
61
66
|
const date = new Date();
|
62
67
|
const dateString = timestamp !== null && timestamp !== void 0 ? timestamp : `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
68
|
+
const renderMessage = () => {
|
69
|
+
if (isLoading) {
|
70
|
+
return React.createElement(MessageLoading, { loadingWord: loadingWord });
|
71
|
+
}
|
72
|
+
if (isEditable) {
|
73
|
+
return (React.createElement(React.Fragment, null,
|
74
|
+
beforeMainContent && React.createElement(React.Fragment, null, beforeMainContent),
|
75
|
+
React.createElement(MessageInput, Object.assign({ content: content, editPlaceholder: editPlaceholder, updateWord: updateWord, cancelWord: cancelWord, onEditUpdate: (event, text) => {
|
76
|
+
onEditUpdate && onEditUpdate(event);
|
77
|
+
setMessageText(text);
|
78
|
+
}, onEditCancel: onEditCancel }, editFormProps))));
|
79
|
+
}
|
80
|
+
return (React.createElement(React.Fragment, null,
|
81
|
+
beforeMainContent && React.createElement(React.Fragment, null, beforeMainContent),
|
82
|
+
error ? (React.createElement(ErrorMessage, Object.assign({}, error))) : (React.createElement(Markdown, { components: {
|
83
|
+
p: (props) => React.createElement(TextMessage, Object.assign({ component: ContentVariants.p }, props)),
|
84
|
+
code: (_a) => {
|
85
|
+
var { children } = _a, props = __rest(_a, ["children"]);
|
86
|
+
return (React.createElement(CodeBlockMessage, Object.assign({}, props, codeBlockProps), children));
|
87
|
+
},
|
88
|
+
h1: (props) => React.createElement(TextMessage, Object.assign({ component: ContentVariants.h1 }, props)),
|
89
|
+
h2: (props) => React.createElement(TextMessage, Object.assign({ component: ContentVariants.h2 }, props)),
|
90
|
+
h3: (props) => React.createElement(TextMessage, Object.assign({ component: ContentVariants.h3 }, props)),
|
91
|
+
h4: (props) => React.createElement(TextMessage, Object.assign({ component: ContentVariants.h4 }, props)),
|
92
|
+
h5: (props) => React.createElement(TextMessage, Object.assign({ component: ContentVariants.h5 }, props)),
|
93
|
+
h6: (props) => React.createElement(TextMessage, Object.assign({ component: ContentVariants.h6 }, props)),
|
94
|
+
blockquote: (props) => React.createElement(TextMessage, Object.assign({ component: ContentVariants.blockquote }, props)),
|
95
|
+
ul: (props) => React.createElement(UnorderedListMessage, Object.assign({}, props)),
|
96
|
+
ol: (props) => React.createElement(OrderedListMessage, Object.assign({}, props)),
|
97
|
+
li: (props) => React.createElement(ListItemMessage, Object.assign({}, props)),
|
98
|
+
table: (props) => React.createElement(TableMessage, Object.assign({}, props, tableProps)),
|
99
|
+
tbody: (props) => React.createElement(TbodyMessage, Object.assign({}, props)),
|
100
|
+
thead: (props) => React.createElement(TheadMessage, Object.assign({}, props)),
|
101
|
+
tr: (props) => React.createElement(TrMessage, Object.assign({}, props)),
|
102
|
+
td: (props) => {
|
103
|
+
// Conflicts with Td type
|
104
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
105
|
+
const { width } = props, rest = __rest(props, ["width"]);
|
106
|
+
return React.createElement(TdMessage, Object.assign({}, rest));
|
107
|
+
},
|
108
|
+
th: (props) => React.createElement(ThMessage, Object.assign({}, props)),
|
109
|
+
img: (props) => React.createElement(ImageMessage, Object.assign({}, props)),
|
110
|
+
a: (props) => (React.createElement(LinkMessage, Object.assign({ href: props.href, rel: props.rel, target: props.target }, linkProps), props.children))
|
111
|
+
}, remarkPlugins: [remarkGfm], rehypePlugins: rehypePlugins }, messageText))));
|
112
|
+
};
|
63
113
|
return (React.createElement("section", Object.assign({ "aria-label": `Message from ${role} - ${dateString}`, className: `pf-chatbot__message pf-chatbot__message--${role}`, "aria-live": isLiveRegion ? 'polite' : undefined, "aria-atomic": isLiveRegion ? false : undefined, ref: innerRef }, props),
|
64
114
|
React.createElement(Avatar, Object.assign({ className: `pf-chatbot__message-avatar ${hasRoundAvatar ? 'pf-chatbot__message-avatar--round' : ''} ${avatarClassName ? avatarClassName : ''}`, src: avatar, alt: "" }, avatarProps)),
|
65
115
|
React.createElement("div", { className: "pf-chatbot__message-contents" },
|
@@ -70,39 +120,8 @@ export const MessageBase = (_a) => {
|
|
70
120
|
React.createElement(Timestamp, { date: date }, timestamp)),
|
71
121
|
React.createElement("div", { className: "pf-chatbot__message-response" },
|
72
122
|
React.createElement("div", { className: "pf-chatbot__message-and-actions" },
|
73
|
-
|
74
|
-
|
75
|
-
error ? (React.createElement(ErrorMessage, Object.assign({}, error))) : (React.createElement(Markdown, { components: {
|
76
|
-
p: (props) => React.createElement(TextMessage, Object.assign({ component: ContentVariants.p }, props)),
|
77
|
-
code: (_a) => {
|
78
|
-
var { children } = _a, props = __rest(_a, ["children"]);
|
79
|
-
return (React.createElement(CodeBlockMessage, Object.assign({}, props, codeBlockProps), children));
|
80
|
-
},
|
81
|
-
h1: (props) => React.createElement(TextMessage, Object.assign({ component: ContentVariants.h1 }, props)),
|
82
|
-
h2: (props) => React.createElement(TextMessage, Object.assign({ component: ContentVariants.h2 }, props)),
|
83
|
-
h3: (props) => React.createElement(TextMessage, Object.assign({ component: ContentVariants.h3 }, props)),
|
84
|
-
h4: (props) => React.createElement(TextMessage, Object.assign({ component: ContentVariants.h4 }, props)),
|
85
|
-
h5: (props) => React.createElement(TextMessage, Object.assign({ component: ContentVariants.h5 }, props)),
|
86
|
-
h6: (props) => React.createElement(TextMessage, Object.assign({ component: ContentVariants.h6 }, props)),
|
87
|
-
blockquote: (props) => React.createElement(TextMessage, Object.assign({ component: ContentVariants.blockquote }, props)),
|
88
|
-
ul: (props) => React.createElement(UnorderedListMessage, Object.assign({}, props)),
|
89
|
-
ol: (props) => React.createElement(OrderedListMessage, Object.assign({}, props)),
|
90
|
-
li: (props) => React.createElement(ListItemMessage, Object.assign({}, props)),
|
91
|
-
table: (props) => React.createElement(TableMessage, Object.assign({}, props, tableProps)),
|
92
|
-
tbody: (props) => React.createElement(TbodyMessage, Object.assign({}, props)),
|
93
|
-
thead: (props) => React.createElement(TheadMessage, Object.assign({}, props)),
|
94
|
-
tr: (props) => React.createElement(TrMessage, Object.assign({}, props)),
|
95
|
-
td: (props) => {
|
96
|
-
// Conflicts with Td type
|
97
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
98
|
-
const { width } = props, rest = __rest(props, ["width"]);
|
99
|
-
return React.createElement(TdMessage, Object.assign({}, rest));
|
100
|
-
},
|
101
|
-
th: (props) => React.createElement(ThMessage, Object.assign({}, props)),
|
102
|
-
img: (props) => React.createElement(ImageMessage, Object.assign({}, props)),
|
103
|
-
a: (props) => (React.createElement(LinkMessage, { href: props.href, rel: props.rel, target: props.target }, props.children))
|
104
|
-
}, remarkPlugins: [remarkGfm], rehypePlugins: rehypePlugins }, content)),
|
105
|
-
afterMainContent && React.createElement(React.Fragment, null, afterMainContent))),
|
123
|
+
renderMessage(),
|
124
|
+
afterMainContent && React.createElement(React.Fragment, null, afterMainContent),
|
106
125
|
!isLoading && sources && React.createElement(SourcesCard, Object.assign({}, sources)),
|
107
126
|
quickStarts && quickStarts.quickStart && (React.createElement(QuickStartTile, { quickStart: quickStarts.quickStart, onSelectQuickStart: quickStarts.onSelectQuickStart, minuteWord: quickStarts.minuteWord, minuteWordPlural: quickStarts.minuteWordPlural, prerequisiteWord: quickStarts.prerequisiteWord, prerequisiteWordPlural: quickStarts.prerequisiteWordPlural, quickStartButtonAriaLabel: quickStarts.quickStartButtonAriaLabel })),
|
108
127
|
!isLoading && actions && React.createElement(ResponseActions, { actions: actions }),
|
@@ -597,6 +597,12 @@ describe('Message', () => {
|
|
597
597
|
// we are mocking rehype libraries, so we can't test target _blank addition on links directly with RTL
|
598
598
|
expect(rehypeExternalLinks).not.toHaveBeenCalled();
|
599
599
|
});
|
600
|
+
it('should handle extra link props correctly', () => __awaiter(void 0, void 0, void 0, function* () {
|
601
|
+
const spy = jest.fn();
|
602
|
+
render(React.createElement(Message, { avatar: "./img", role: "user", name: "User", content: `[PatternFly](https://www.patternfly.org/)`, linkProps: { onClick: spy } }));
|
603
|
+
yield userEvent.click(screen.getByRole('link', { name: /PatternFly/i }));
|
604
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
605
|
+
}));
|
600
606
|
it('should handle error correctly', () => {
|
601
607
|
render(React.createElement(Message, { avatar: "./img", role: "user", name: "User", error: ERROR }));
|
602
608
|
expect(screen.getByRole('heading', { name: /Could not load chat/i })).toBeTruthy();
|
@@ -614,4 +620,50 @@ describe('Message', () => {
|
|
614
620
|
expect(screen.getByRole('heading', { name: /Could not load chat/i })).toBeTruthy();
|
615
621
|
expect(screen.queryByText('Test')).toBeFalsy();
|
616
622
|
});
|
623
|
+
it('should handle isEditable when there is message content', () => {
|
624
|
+
render(React.createElement(Message, { avatar: "./img", role: "user", name: "User", isEditable: true, content: "Test" }));
|
625
|
+
expect(screen.getByRole('textbox')).toBeTruthy();
|
626
|
+
expect(screen.getByRole('textbox')).toHaveValue('Test');
|
627
|
+
expect(screen.getByRole('button', { name: /Update/i })).toBeTruthy();
|
628
|
+
expect(screen.getByRole('button', { name: /Cancel/i })).toBeTruthy();
|
629
|
+
});
|
630
|
+
it('should handle isEditable when there is no message content', () => {
|
631
|
+
render(React.createElement(Message, { avatar: "./img", role: "user", name: "User", isEditable: true }));
|
632
|
+
expect(screen.getByRole('textbox')).toBeTruthy();
|
633
|
+
expect(screen.getByRole('textbox')).toHaveValue('');
|
634
|
+
expect(screen.getByRole('textbox')).toHaveAttribute('placeholder', 'Edit prompt message...');
|
635
|
+
expect(screen.getByRole('button', { name: /Update/i })).toBeTruthy();
|
636
|
+
expect(screen.getByRole('button', { name: /Cancel/i })).toBeTruthy();
|
637
|
+
});
|
638
|
+
it('should be able to change edit placeholder', () => {
|
639
|
+
render(React.createElement(Message, { avatar: "./img", role: "user", name: "User", isEditable: true, editPlaceholder: "I am a placeholder" }));
|
640
|
+
expect(screen.getByRole('textbox')).toBeTruthy();
|
641
|
+
expect(screen.getByRole('textbox')).toHaveValue('');
|
642
|
+
expect(screen.getByRole('textbox')).toHaveAttribute('placeholder', 'I am a placeholder');
|
643
|
+
});
|
644
|
+
it('should be able to change updateWord', () => {
|
645
|
+
render(React.createElement(Message, { avatar: "./img", role: "user", name: "User", isEditable: true, updateWord: "Submit" }));
|
646
|
+
expect(screen.getByRole('button', { name: /Submit/i })).toBeTruthy();
|
647
|
+
});
|
648
|
+
it('should be able to change cancelWord', () => {
|
649
|
+
render(React.createElement(Message, { avatar: "./img", role: "user", name: "User", isEditable: true, cancelWord: "Don't submit" }));
|
650
|
+
expect(screen.getByRole('button', { name: /Don't submit/i })).toBeTruthy();
|
651
|
+
});
|
652
|
+
it('should be able to add onEditUpdate', () => __awaiter(void 0, void 0, void 0, function* () {
|
653
|
+
const spy = jest.fn();
|
654
|
+
render(React.createElement(Message, { avatar: "./img", role: "user", name: "User", isEditable: true, onEditUpdate: spy }));
|
655
|
+
yield userEvent.click(screen.getByRole('button', { name: /Update/i }));
|
656
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
657
|
+
}));
|
658
|
+
it('should be able to add onEditCancel', () => __awaiter(void 0, void 0, void 0, function* () {
|
659
|
+
const spy = jest.fn();
|
660
|
+
render(React.createElement(Message, { avatar: "./img", role: "user", name: "User", isEditable: true, onEditCancel: spy }));
|
661
|
+
yield userEvent.click(screen.getByRole('button', { name: /Cancel/i }));
|
662
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
663
|
+
}));
|
664
|
+
it('should be able to add editFormProps', () => {
|
665
|
+
const { container } = render(React.createElement(Message, { avatar: "./img", role: "user", name: "User", isEditable: true, editFormProps: { className: 'test' } }));
|
666
|
+
const form = container.querySelector('form');
|
667
|
+
expect(form).toHaveClass('test');
|
668
|
+
});
|
617
669
|
});
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { FormProps } from '@patternfly/react-core';
|
3
|
+
export interface MessageInputProps extends FormProps {
|
4
|
+
/** Placeholder for edit input */
|
5
|
+
editPlaceholder?: string;
|
6
|
+
/** Label for the English word "Update" used in edit mode. */
|
7
|
+
updateWord?: string;
|
8
|
+
/** Label for the English word "Cancel" used in edit mode. */
|
9
|
+
cancelWord?: string;
|
10
|
+
/** Callback function for when edit mode update button is clicked */
|
11
|
+
onEditUpdate?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>, value: string) => void;
|
12
|
+
/** Callback functionf or when edit cancel update button is clicked */
|
13
|
+
onEditCancel?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
14
|
+
/** Message text */
|
15
|
+
content?: string;
|
16
|
+
}
|
17
|
+
declare const MessageInput: React.FunctionComponent<MessageInputProps>;
|
18
|
+
export default MessageInput;
|
@@ -0,0 +1,29 @@
|
|
1
|
+
// ============================================================================
|
2
|
+
// Chatbot Main - Message Input
|
3
|
+
// ============================================================================
|
4
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
5
|
+
var t = {};
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
7
|
+
t[p] = s[p];
|
8
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
9
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
10
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
11
|
+
t[p[i]] = s[p[i]];
|
12
|
+
}
|
13
|
+
return t;
|
14
|
+
};
|
15
|
+
import React from 'react';
|
16
|
+
import { ActionGroup, Button, Form, TextArea } from '@patternfly/react-core';
|
17
|
+
const MessageInput = (_a) => {
|
18
|
+
var { editPlaceholder = 'Edit prompt message...', updateWord = 'Update', cancelWord = 'Cancel', onEditUpdate, onEditCancel, content } = _a, props = __rest(_a, ["editPlaceholder", "updateWord", "cancelWord", "onEditUpdate", "onEditCancel", "content"]);
|
19
|
+
const [messageText, setMessageText] = React.useState(content !== null && content !== void 0 ? content : '');
|
20
|
+
const onChange = (event, value) => {
|
21
|
+
setMessageText(value);
|
22
|
+
};
|
23
|
+
return (React.createElement(Form, Object.assign({}, props),
|
24
|
+
React.createElement(TextArea, { placeholder: editPlaceholder, value: messageText, onChange: onChange, "aria-label": editPlaceholder, autoResize: true }),
|
25
|
+
React.createElement(ActionGroup, { className: "pf-chatbot__message-edit-buttons" },
|
26
|
+
React.createElement(Button, { variant: "primary", onClick: (event) => onEditUpdate && onEditUpdate(event, messageText) }, updateWord),
|
27
|
+
React.createElement(Button, { variant: "secondary", onClick: onEditCancel }, cancelWord))));
|
28
|
+
};
|
29
|
+
export default MessageInput;
|
@@ -23,26 +23,26 @@ const MessageBoxBase = ({ announcement, ariaLabel = 'Scrollable message log', ch
|
|
23
23
|
setAtTop(scrollTop === 0);
|
24
24
|
setAtBottom(Math.round(scrollTop) + Math.round(clientHeight) >= Math.round(scrollHeight) - 1); // rounding means it could be within a pixel of the bottom
|
25
25
|
}
|
26
|
-
}, []);
|
26
|
+
}, [messageBoxRef]);
|
27
27
|
const checkOverflow = React.useCallback(() => {
|
28
28
|
const element = messageBoxRef.current;
|
29
29
|
if (element) {
|
30
30
|
const { scrollHeight, clientHeight } = element;
|
31
31
|
setIsOverflowing(scrollHeight >= clientHeight);
|
32
32
|
}
|
33
|
-
}, []);
|
33
|
+
}, [messageBoxRef]);
|
34
34
|
const scrollToTop = React.useCallback(() => {
|
35
35
|
const element = messageBoxRef.current;
|
36
36
|
if (element) {
|
37
37
|
element.scrollTo({ top: 0, behavior: 'smooth' });
|
38
38
|
}
|
39
|
-
}, []);
|
39
|
+
}, [messageBoxRef]);
|
40
40
|
const scrollToBottom = React.useCallback(() => {
|
41
41
|
const element = messageBoxRef.current;
|
42
42
|
if (element) {
|
43
43
|
element.scrollTo({ top: element.scrollHeight, behavior: 'smooth' });
|
44
44
|
}
|
45
|
-
}, []);
|
45
|
+
}, [messageBoxRef]);
|
46
46
|
// Detect scroll position
|
47
47
|
React.useEffect(() => {
|
48
48
|
const element = messageBoxRef.current;
|
@@ -56,7 +56,7 @@ const MessageBoxBase = ({ announcement, ariaLabel = 'Scrollable message log', ch
|
|
56
56
|
element.removeEventListener('scroll', handleScroll);
|
57
57
|
};
|
58
58
|
}
|
59
|
-
}, [checkOverflow, handleScroll]);
|
59
|
+
}, [checkOverflow, handleScroll, messageBoxRef]);
|
60
60
|
return (React.createElement(React.Fragment, null,
|
61
61
|
React.createElement(JumpButton, { position: "top", isHidden: isOverflowing && atTop, onClick: scrollToTop }),
|
62
62
|
React.createElement("div", { role: "region", tabIndex: 0, "aria-label": ariaLabel, className: `pf-chatbot__messagebox ${position === 'bottom' && 'pf-chatbot__messagebox--bottom'} ${className !== null && className !== void 0 ? className : ''}`, ref: innerRef !== null && innerRef !== void 0 ? innerRef : messageBoxRef },
|
@@ -5,7 +5,7 @@ export interface SourcesCardProps extends CardProps {
|
|
5
5
|
className?: string;
|
6
6
|
/** Flag indicating if the pagination is disabled. */
|
7
7
|
isDisabled?: boolean;
|
8
|
-
/** Label for the English word "of"
|
8
|
+
/** @deprecated ofWord has been deprecated. Label for the English word "of." */
|
9
9
|
ofWord?: string;
|
10
10
|
/** Accessible label for the pagination component. */
|
11
11
|
paginationAriaLabel?: string;
|
@@ -14,6 +14,8 @@ export interface SourcesCardProps extends CardProps {
|
|
14
14
|
title?: string;
|
15
15
|
link: string;
|
16
16
|
body?: React.ReactNode | string;
|
17
|
+
isExternal?: boolean;
|
18
|
+
hasShowMore?: boolean;
|
17
19
|
}[];
|
18
20
|
/** Label for the English word "source" */
|
19
21
|
sourceWord?: string;
|
@@ -29,6 +31,10 @@ export interface SourcesCardProps extends CardProps {
|
|
29
31
|
onPreviousClick?: (event: React.SyntheticEvent<HTMLButtonElement>, page: number) => void;
|
30
32
|
/** Function called when page is changed. */
|
31
33
|
onSetPage?: (event: React.MouseEvent | React.KeyboardEvent | MouseEvent, newPage: number) => void;
|
34
|
+
/** Label for English words "show more" */
|
35
|
+
showMoreWords?: string;
|
36
|
+
/** Label for English words "show less" */
|
37
|
+
showLessWords?: string;
|
32
38
|
}
|
33
39
|
declare const SourcesCard: React.FunctionComponent<SourcesCardProps>;
|
34
40
|
export default SourcesCard;
|
@@ -14,10 +14,15 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
14
14
|
// ============================================================================
|
15
15
|
import React from 'react';
|
16
16
|
// Import PatternFly components
|
17
|
-
import { Button, ButtonVariant, Card, CardBody, CardFooter, CardTitle, Icon, pluralize, Truncate } from '@patternfly/react-core';
|
17
|
+
import { Button, ButtonVariant, Card, CardBody, CardFooter, CardTitle, ExpandableSection, ExpandableSectionVariant, Icon, pluralize, Truncate } from '@patternfly/react-core';
|
18
|
+
import { ExternalLinkSquareAltIcon } from '@patternfly/react-icons';
|
18
19
|
const SourcesCard = (_a) => {
|
19
|
-
var { className, isDisabled,
|
20
|
+
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' } = _a, props = __rest(_a, ["className", "isDisabled", "paginationAriaLabel", "sources", "sourceWord", "sourceWordPlural", "toNextPageAriaLabel", "toPreviousPageAriaLabel", "onNextClick", "onPreviousClick", "onSetPage", "showMoreWords", "showLessWords"]);
|
20
21
|
const [page, setPage] = React.useState(1);
|
22
|
+
const [isExpanded, setIsExpanded] = React.useState(false);
|
23
|
+
const onToggle = (_event, isExpanded) => {
|
24
|
+
setIsExpanded(isExpanded);
|
25
|
+
};
|
21
26
|
const handleNewPage = (_evt, newPage) => {
|
22
27
|
setPage(newPage);
|
23
28
|
onSetPage && onSetPage(_evt, newPage);
|
@@ -32,8 +37,11 @@ const SourcesCard = (_a) => {
|
|
32
37
|
React.createElement("span", null, pluralize(sources.length, sourceWord, sourceWordPlural)),
|
33
38
|
React.createElement(Card, Object.assign({ className: "pf-chatbot__sources-card" }, props),
|
34
39
|
React.createElement(CardTitle, { className: "pf-chatbot__sources-card-title" },
|
35
|
-
React.createElement("a",
|
36
|
-
sources[page - 1].body && (React.createElement(CardBody, { className: `pf-chatbot__sources-card-body
|
40
|
+
React.createElement(Button, { component: "a", variant: ButtonVariant.link, href: sources[page - 1].link, icon: sources[page - 1].isExternal ? React.createElement(ExternalLinkSquareAltIcon, null) : undefined, iconPosition: "end", isInline: true, rel: sources[page - 1].isExternal ? 'noreferrer' : undefined, target: sources[page - 1].isExternal ? '_blank' : undefined }, renderTitle(sources[page - 1].title))),
|
41
|
+
sources[page - 1].body && (React.createElement(CardBody, { className: `pf-chatbot__sources-card-body` }, sources[page - 1].hasShowMore ? (
|
42
|
+
// prevents extra VO announcements of button text - parent Message has aria-live
|
43
|
+
React.createElement("div", { "aria-live": "off" },
|
44
|
+
React.createElement(ExpandableSection, { variant: ExpandableSectionVariant.truncate, toggleText: isExpanded ? showLessWords : showMoreWords, onToggle: onToggle, isExpanded: isExpanded, truncateMaxLines: 2 }, sources[page - 1].body))) : (React.createElement("div", { className: "pf-chatbot__sources-card-body-text" }, sources[page - 1].body)))),
|
37
45
|
sources.length > 1 && (React.createElement(CardFooter, { className: "pf-chatbot__sources-card-footer-container" },
|
38
46
|
React.createElement("div", { className: "pf-chatbot__sources-card-footer" },
|
39
47
|
React.createElement("nav", { className: `pf-chatbot__sources-card-footer-buttons ${className}`, "aria-label": paginationAriaLabel },
|
@@ -45,6 +53,10 @@ const SourcesCard = (_a) => {
|
|
45
53
|
React.createElement(Icon, { iconSize: "lg" },
|
46
54
|
React.createElement("svg", { className: "pf-v6-svg", viewBox: "0 0 280 500", fill: "currentColor", "aria-hidden": "true", role: "img", width: "1em", height: "1em" },
|
47
55
|
React.createElement("path", { d: "M31.7 239l136-136c9.4-9.4 24.6-9.4 33.9 0l22.6 22.6c9.4 9.4 9.4 24.6 0 33.9L127.9 256l96.4 96.4c9.4 9.4 9.4 24.6 0 33.9L201.7 409c-9.4 9.4-24.6 9.4-33.9 0l-136-136c-9.5-9.4-9.5-24.6-.1-34z" })))),
|
56
|
+
React.createElement("span", { "aria-hidden": "true" },
|
57
|
+
page,
|
58
|
+
"/",
|
59
|
+
sources.length),
|
48
60
|
React.createElement(Button, { variant: ButtonVariant.plain, isDisabled: isDisabled || page === sources.length, "aria-label": toNextPageAriaLabel, "data-action": "next", onClick: (event) => {
|
49
61
|
const newPage = page + 1 <= sources.length ? page + 1 : sources.length;
|
50
62
|
onNextClick && onNextClick(event, newPage);
|
@@ -52,12 +64,6 @@ const SourcesCard = (_a) => {
|
|
52
64
|
} },
|
53
65
|
React.createElement(Icon, { isInline: true, iconSize: "lg" },
|
54
66
|
React.createElement("svg", { className: "pf-v6-svg", viewBox: "0 0 180 500", fill: "currentColor", "aria-hidden": "true", role: "img", width: "1em", height: "1em" },
|
55
|
-
React.createElement("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" })))))
|
56
|
-
React.createElement("span", { "aria-hidden": "true" },
|
57
|
-
page,
|
58
|
-
" ",
|
59
|
-
ofWord,
|
60
|
-
" ",
|
61
|
-
sources.length)))))));
|
67
|
+
React.createElement("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" })))))))))));
|
62
68
|
};
|
63
69
|
export default SourcesCard;
|
@@ -13,17 +13,13 @@ import userEvent from '@testing-library/user-event';
|
|
13
13
|
import '@testing-library/jest-dom';
|
14
14
|
import SourcesCard from './SourcesCard';
|
15
15
|
describe('SourcesCard', () => {
|
16
|
-
it('should render card', () => {
|
17
|
-
const { container } = render(React.createElement(SourcesCard, { sources: [{ link: '' }] }));
|
18
|
-
expect(container).toMatchSnapshot();
|
19
|
-
});
|
20
16
|
it('should render card correctly if one source with only a link is passed in', () => {
|
21
17
|
render(React.createElement(SourcesCard, { sources: [{ link: '' }] }));
|
22
18
|
expect(screen.getByText('1 source')).toBeTruthy();
|
23
19
|
expect(screen.getByText('Source 1')).toBeTruthy();
|
24
20
|
// no buttons or navigation when there is only 1 source
|
25
21
|
expect(screen.queryByRole('button')).toBeFalsy();
|
26
|
-
expect(screen.queryByText('1
|
22
|
+
expect(screen.queryByText('1/1')).toBeFalsy();
|
27
23
|
});
|
28
24
|
it('should render card correctly if one source with a title is passed in', () => {
|
29
25
|
render(React.createElement(SourcesCard, { sources: [{ title: 'How to make an apple pie', link: '' }] }));
|
@@ -48,7 +44,7 @@ describe('SourcesCard', () => {
|
|
48
44
|
] }));
|
49
45
|
expect(screen.getByText('2 sources')).toBeTruthy();
|
50
46
|
expect(screen.getByText('How to make an apple pie')).toBeTruthy();
|
51
|
-
expect(screen.getByText('1
|
47
|
+
expect(screen.getByText('1/2')).toBeTruthy();
|
52
48
|
screen.getByRole('button', { name: /Go to previous page/i });
|
53
49
|
screen.getByRole('button', { name: /Go to next page/i });
|
54
50
|
});
|
@@ -58,12 +54,12 @@ describe('SourcesCard', () => {
|
|
58
54
|
{ title: 'How to make cookies', link: '' }
|
59
55
|
] }));
|
60
56
|
expect(screen.getByText('How to make an apple pie')).toBeTruthy();
|
61
|
-
expect(screen.getByText('1
|
57
|
+
expect(screen.getByText('1/2')).toBeTruthy();
|
62
58
|
expect(screen.getByRole('button', { name: /Go to previous page/i })).toBeDisabled();
|
63
59
|
yield userEvent.click(screen.getByRole('button', { name: /Go to next page/i }));
|
64
60
|
expect(screen.queryByText('How to make an apple pie')).toBeFalsy();
|
65
61
|
expect(screen.getByText('How to make cookies')).toBeTruthy();
|
66
|
-
expect(screen.getByText('2
|
62
|
+
expect(screen.getByText('2/2')).toBeTruthy();
|
67
63
|
expect(screen.getByRole('button', { name: /Go to previous page/i })).toBeEnabled();
|
68
64
|
expect(screen.getByRole('button', { name: /Go to next page/i })).toBeDisabled();
|
69
65
|
}));
|
@@ -83,13 +79,6 @@ describe('SourcesCard', () => {
|
|
83
79
|
expect(screen.getByRole('button', { name: /Go to previous page/i })).toBeDisabled();
|
84
80
|
expect(screen.getByRole('button', { name: /Go to next page/i })).toBeDisabled();
|
85
81
|
});
|
86
|
-
it('should change ofWord appropriately', () => {
|
87
|
-
render(React.createElement(SourcesCard, { sources: [
|
88
|
-
{ title: 'How to make an apple pie', link: '' },
|
89
|
-
{ title: 'How to make cookies', link: '' }
|
90
|
-
], ofWord: 'de' }));
|
91
|
-
expect(screen.getByText('1 de 2')).toBeTruthy();
|
92
|
-
});
|
93
82
|
it('should render navigation aria label appropriately', () => {
|
94
83
|
render(React.createElement(SourcesCard, { sources: [
|
95
84
|
{ title: 'How to make an apple pie', link: '' },
|
@@ -159,4 +148,25 @@ describe('SourcesCard', () => {
|
|
159
148
|
yield userEvent.click(screen.getByRole('button', { name: /Go to previous page/i }));
|
160
149
|
expect(spy).toHaveBeenCalledTimes(2);
|
161
150
|
}));
|
151
|
+
it('should handle showMore appropriately', () => __awaiter(void 0, void 0, void 0, function* () {
|
152
|
+
render(React.createElement(SourcesCard, { sources: [
|
153
|
+
{
|
154
|
+
title: 'Getting started with Red Hat OpenShift',
|
155
|
+
link: '#',
|
156
|
+
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 ...',
|
157
|
+
hasShowMore: true
|
158
|
+
},
|
159
|
+
{
|
160
|
+
title: 'Azure Red Hat OpenShift documentation',
|
161
|
+
link: '#',
|
162
|
+
body: 'Microsoft Azure Red Hat OpenShift allows you to deploy a production ready Red Hat OpenShift cluster in Azure ...'
|
163
|
+
},
|
164
|
+
{
|
165
|
+
title: 'OKD Documentation: Home',
|
166
|
+
link: '#',
|
167
|
+
body: 'OKD is a distribution of Kubernetes optimized for continuous application development and multi-tenant deployment. OKD also serves as the upstream code base upon ...'
|
168
|
+
}
|
169
|
+
] }));
|
170
|
+
expect(screen.getByRole('region')).toHaveAttribute('class', 'pf-v6-c-expandable-section__content');
|
171
|
+
}));
|
162
172
|
});
|
@@ -1,10 +1,9 @@
|
|
1
|
-
import { TrackingSpi } from './tracking_spi';
|
1
|
+
import { InitProps, TrackingSpi } from './tracking_spi';
|
2
2
|
import { TrackingApi, TrackingEventProperties } from './tracking_api';
|
3
3
|
export declare class ConsoleTrackingProvider implements TrackingSpi, TrackingApi {
|
4
|
+
private verbose;
|
4
5
|
trackPageView(url: string | undefined): void;
|
5
|
-
|
6
|
-
|
7
|
-
identify(userID: string): void;
|
6
|
+
initialize(props: InitProps): void;
|
7
|
+
identify(userID: string, userProperties?: TrackingEventProperties): void;
|
8
8
|
trackSingleItem(item: string, properties?: TrackingEventProperties): void;
|
9
|
-
getKey(): string;
|
10
9
|
}
|
@@ -1,23 +1,30 @@
|
|
1
1
|
export class ConsoleTrackingProvider {
|
2
|
+
constructor() {
|
3
|
+
this.verbose = false;
|
4
|
+
}
|
2
5
|
trackPageView(url) {
|
3
|
-
|
4
|
-
|
6
|
+
if (this.verbose) {
|
7
|
+
// eslint-disable-next-line no-console
|
8
|
+
console.log('ConsoleProvider pageView ', url);
|
9
|
+
}
|
5
10
|
}
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
+
initialize(props) {
|
12
|
+
this.verbose = props.verbose;
|
13
|
+
if (this.verbose) {
|
14
|
+
// eslint-disable-next-line no-console
|
15
|
+
console.log('ConsoleProvider initialize');
|
16
|
+
}
|
11
17
|
}
|
12
|
-
identify(userID) {
|
13
|
-
|
14
|
-
|
18
|
+
identify(userID, userProperties = {}) {
|
19
|
+
if (this.verbose) {
|
20
|
+
// eslint-disable-next-line no-console
|
21
|
+
console.log('ConsoleProvider identify ', userID, userProperties);
|
22
|
+
}
|
15
23
|
}
|
16
24
|
trackSingleItem(item, properties) {
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
return 'console';
|
25
|
+
if (this.verbose) {
|
26
|
+
// eslint-disable-next-line no-console
|
27
|
+
console.log('ConsoleProvider: ' + item, properties);
|
28
|
+
}
|
22
29
|
}
|
23
30
|
}
|
@@ -1,9 +1,9 @@
|
|
1
1
|
import { TrackingApi, TrackingEventProperties } from './tracking_api';
|
2
2
|
import { InitProps, TrackingSpi } from './tracking_spi';
|
3
3
|
export declare class PosthogTrackingProvider implements TrackingSpi, TrackingApi {
|
4
|
-
|
4
|
+
private verbose;
|
5
5
|
initialize(props: InitProps): void;
|
6
|
-
identify(userID: string): void;
|
6
|
+
identify(userID: string, userProperties?: TrackingEventProperties): void;
|
7
7
|
trackPageView(url: string | undefined): void;
|
8
8
|
trackSingleItem(item: string, properties?: TrackingEventProperties): void;
|
9
9
|
}
|