@patternfly/chatbot 6.4.0-prerelease.20 → 6.4.0-prerelease.22
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.js +1 -1
- package/dist/cjs/FileDetails/FileDetails.d.ts +22 -3
- package/dist/cjs/FileDetails/FileDetails.js +27 -912
- package/dist/cjs/FileDetails/FileDetails.test.js +16 -0
- package/dist/cjs/FileDetailsLabel/FileDetailsLabel.d.ts +8 -2
- package/dist/cjs/FileDetailsLabel/FileDetailsLabel.js +14 -2
- package/dist/cjs/FileDetailsLabel/FileDetailsLabel.test.js +19 -1
- package/dist/cjs/ImagePreview/ImagePreview.d.ts +53 -0
- package/dist/cjs/ImagePreview/ImagePreview.js +47 -0
- package/dist/cjs/ImagePreview/ImagePreview.test.d.ts +1 -0
- package/dist/cjs/ImagePreview/ImagePreview.test.js +225 -0
- package/dist/cjs/ImagePreview/index.d.ts +2 -0
- package/dist/cjs/ImagePreview/index.js +23 -0
- package/dist/cjs/Message/Message.d.ts +3 -0
- package/dist/cjs/Message/Message.js +3 -2
- package/dist/cjs/MessageBox/MessageBox.js +1 -1
- package/dist/cjs/ToolCall/ToolCall.d.ts +44 -0
- package/dist/cjs/ToolCall/ToolCall.js +14 -0
- package/dist/cjs/ToolCall/ToolCall.test.d.ts +1 -0
- package/dist/cjs/ToolCall/ToolCall.test.js +144 -0
- package/dist/cjs/ToolCall/index.d.ts +2 -0
- package/dist/cjs/ToolCall/index.js +23 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.js +7 -1
- package/dist/css/main.css +104 -19
- package/dist/css/main.css.map +1 -1
- package/dist/dynamic/ImagePreview/package.json +1 -0
- package/dist/dynamic/ToolCall/package.json +1 -0
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +1 -1
- package/dist/esm/FileDetails/FileDetails.d.ts +22 -3
- package/dist/esm/FileDetails/FileDetails.js +27 -912
- package/dist/esm/FileDetails/FileDetails.test.js +16 -0
- package/dist/esm/FileDetailsLabel/FileDetailsLabel.d.ts +8 -2
- package/dist/esm/FileDetailsLabel/FileDetailsLabel.js +14 -2
- package/dist/esm/FileDetailsLabel/FileDetailsLabel.test.js +19 -1
- package/dist/esm/ImagePreview/ImagePreview.d.ts +53 -0
- package/dist/esm/ImagePreview/ImagePreview.js +42 -0
- package/dist/esm/ImagePreview/ImagePreview.test.d.ts +1 -0
- package/dist/esm/ImagePreview/ImagePreview.test.js +220 -0
- package/dist/esm/ImagePreview/index.d.ts +2 -0
- package/dist/esm/ImagePreview/index.js +2 -0
- package/dist/esm/Message/Message.d.ts +3 -0
- package/dist/esm/Message/Message.js +3 -2
- package/dist/esm/MessageBox/MessageBox.js +1 -1
- package/dist/esm/ToolCall/ToolCall.d.ts +44 -0
- package/dist/esm/ToolCall/ToolCall.js +10 -0
- package/dist/esm/ToolCall/ToolCall.test.d.ts +1 -0
- package/dist/esm/ToolCall/ToolCall.test.js +139 -0
- package/dist/esm/ToolCall/index.d.ts +2 -0
- package/dist/esm/ToolCall/index.js +2 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +4 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/AttachmentEdit.tsx +1 -1
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/ImagePreview.tsx +53 -0
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithToolCall.tsx +45 -0
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +21 -1
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/PreviewAttachment.tsx +1 -1
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/file-preview.svg +9 -0
- package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +1 -1
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +0 -12
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +1 -1
- package/src/FileDetails/FileDetails.scss +10 -0
- package/src/FileDetails/FileDetails.test.tsx +16 -0
- package/src/FileDetails/FileDetails.tsx +89 -32
- package/src/FileDetails/__snapshots__/FileDetails.test.tsx.snap +25 -16
- package/src/FileDetailsLabel/FileDetailsLabel.test.tsx +21 -1
- package/src/FileDetailsLabel/FileDetailsLabel.tsx +16 -3
- package/src/FileDetailsLabel/__snapshots__/FileDetailsLabel.test.tsx.snap +25 -16
- package/src/ImagePreview/ImagePreview.scss +61 -0
- package/src/ImagePreview/ImagePreview.test.tsx +253 -0
- package/src/ImagePreview/ImagePreview.tsx +200 -0
- package/src/ImagePreview/index.ts +3 -0
- package/src/Message/Message.tsx +5 -0
- package/src/MessageBox/MessageBox.scss +0 -12
- package/src/MessageBox/MessageBox.tsx +1 -1
- package/src/ToolCall/ToolCall.scss +37 -0
- package/src/ToolCall/ToolCall.test.tsx +184 -0
- package/src/ToolCall/ToolCall.tsx +147 -0
- package/src/ToolCall/index.ts +3 -0
- package/src/index.ts +6 -0
- package/src/main.scss +14 -0
|
@@ -9,6 +9,7 @@ exports[`FileDetails should render file details 1`] = `
|
|
|
9
9
|
class="pf-v6-l-flex pf-m-align-items-center pf-m-align-self-center pf-m-justify-content-center pf-chatbot__code-icon"
|
|
10
10
|
>
|
|
11
11
|
<svg
|
|
12
|
+
aria-hidden="true"
|
|
12
13
|
fill="currentColor"
|
|
13
14
|
height="24"
|
|
14
15
|
viewBox="0 0 24 24"
|
|
@@ -40,30 +41,38 @@ exports[`FileDetails should render file details 1`] = `
|
|
|
40
41
|
</svg>
|
|
41
42
|
</div>
|
|
42
43
|
<div
|
|
43
|
-
class="pf-v6-l-
|
|
44
|
+
class="pf-v6-l-flex pf-m-gap-xs"
|
|
44
45
|
>
|
|
45
46
|
<div
|
|
46
|
-
class="
|
|
47
|
+
class=""
|
|
47
48
|
>
|
|
48
|
-
<
|
|
49
|
-
class="pf-
|
|
49
|
+
<div
|
|
50
|
+
class="pf-v6-l-flex pf-m-column pf-m-gap-none"
|
|
50
51
|
>
|
|
51
|
-
<
|
|
52
|
-
class="
|
|
53
|
-
tabindex="0"
|
|
52
|
+
<div
|
|
53
|
+
class=""
|
|
54
54
|
>
|
|
55
55
|
<span
|
|
56
|
-
class="pf-
|
|
56
|
+
class="pf-chatbot__code-fileName"
|
|
57
57
|
>
|
|
58
|
-
|
|
58
|
+
<span
|
|
59
|
+
class="pf-v6-c-truncate"
|
|
60
|
+
tabindex="0"
|
|
61
|
+
>
|
|
62
|
+
<span
|
|
63
|
+
class="pf-v6-c-truncate__start"
|
|
64
|
+
>
|
|
65
|
+
test
|
|
66
|
+
</span>
|
|
67
|
+
</span>
|
|
59
68
|
</span>
|
|
60
|
-
</
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
</div>
|
|
70
|
+
<div
|
|
71
|
+
class="pf-chatbot__code-language"
|
|
72
|
+
>
|
|
73
|
+
TEXT
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
67
76
|
</div>
|
|
68
77
|
</div>
|
|
69
78
|
</div>
|
|
@@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react';
|
|
|
2
2
|
import '@testing-library/jest-dom';
|
|
3
3
|
import FileDetailsLabel from './FileDetailsLabel';
|
|
4
4
|
import userEvent from '@testing-library/user-event';
|
|
5
|
+
import { BellIcon } from '@patternfly/react-icons';
|
|
5
6
|
|
|
6
7
|
describe('FileDetailsLabel', () => {
|
|
7
8
|
it('should render file details label', () => {
|
|
@@ -18,6 +19,19 @@ describe('FileDetailsLabel', () => {
|
|
|
18
19
|
expect(screen.getByText('test')).toBeTruthy();
|
|
19
20
|
expect(screen.queryByTestId('language')).toBeFalsy();
|
|
20
21
|
});
|
|
22
|
+
it('should pass file size down', () => {
|
|
23
|
+
render(<FileDetailsLabel fileName="test.svg" fileSize="100MB" />);
|
|
24
|
+
expect(screen.getByText('100MB')).toBeTruthy();
|
|
25
|
+
});
|
|
26
|
+
it('should pass truncation prop down as true by default', () => {
|
|
27
|
+
render(<FileDetailsLabel fileName="test.svg" />);
|
|
28
|
+
expect(screen.getByText('test')).toBeTruthy();
|
|
29
|
+
expect(screen.queryByText('test.svg')).toBeFalsy();
|
|
30
|
+
});
|
|
31
|
+
it('should pass truncation prop down when false', () => {
|
|
32
|
+
render(<FileDetailsLabel fileName="test.svg" hasTruncation={false} />);
|
|
33
|
+
expect(screen.getByText('test.svg')).toBeTruthy();
|
|
34
|
+
});
|
|
21
35
|
it('should not show spinner by default', () => {
|
|
22
36
|
render(<FileDetailsLabel fileName="test.txt" spinnerTestId="spinner" />);
|
|
23
37
|
expect(screen.queryByTestId('spinner')).toBeFalsy();
|
|
@@ -42,6 +56,12 @@ describe('FileDetailsLabel', () => {
|
|
|
42
56
|
});
|
|
43
57
|
it('should use closeButtonAriaLabel prop appropriately', () => {
|
|
44
58
|
render(<FileDetailsLabel fileName="test.txt" onClose={jest.fn()} closeButtonAriaLabel="Delete file" />);
|
|
45
|
-
screen.getByRole('button', { name: /Delete file/i });
|
|
59
|
+
expect(screen.getByRole('button', { name: /Delete file/i })).toBeTruthy();
|
|
60
|
+
});
|
|
61
|
+
it('should support custom close icon', () => {
|
|
62
|
+
render(
|
|
63
|
+
<FileDetailsLabel fileName="test.txt" onClose={jest.fn()} closeButtonIcon={<BellIcon data-testid="bell" />} />
|
|
64
|
+
);
|
|
65
|
+
expect(screen.getByTestId('bell')).toBeTruthy();
|
|
46
66
|
});
|
|
47
67
|
});
|
|
@@ -4,7 +4,7 @@ import FileDetails from '../FileDetails';
|
|
|
4
4
|
import { Spinner } from '@patternfly/react-core';
|
|
5
5
|
import { TimesIcon } from '@patternfly/react-icons';
|
|
6
6
|
|
|
7
|
-
interface FileDetailsLabelProps {
|
|
7
|
+
export interface FileDetailsLabelProps {
|
|
8
8
|
/** Name of file, including extension */
|
|
9
9
|
fileName: string;
|
|
10
10
|
/** Unique id of file */
|
|
@@ -21,6 +21,12 @@ interface FileDetailsLabelProps {
|
|
|
21
21
|
languageTestId?: string;
|
|
22
22
|
/** Custom test id for the loading spinner in the component */
|
|
23
23
|
spinnerTestId?: string;
|
|
24
|
+
/** File size */
|
|
25
|
+
fileSize?: string;
|
|
26
|
+
/** Whether to truncate file name */
|
|
27
|
+
hasTruncation?: boolean;
|
|
28
|
+
/** Icon used for close button */
|
|
29
|
+
closeButtonIcon?: React.ReactNode;
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
export const FileDetailsLabel = ({
|
|
@@ -31,7 +37,11 @@ export const FileDetailsLabel = ({
|
|
|
31
37
|
onClose,
|
|
32
38
|
closeButtonAriaLabel,
|
|
33
39
|
languageTestId,
|
|
34
|
-
spinnerTestId
|
|
40
|
+
spinnerTestId,
|
|
41
|
+
fileSize,
|
|
42
|
+
hasTruncation = true,
|
|
43
|
+
closeButtonIcon = <TimesIcon />,
|
|
44
|
+
...props
|
|
35
45
|
}: PropsWithChildren<FileDetailsLabelProps>) => {
|
|
36
46
|
const handleClose = (event) => {
|
|
37
47
|
onClose && onClose(event, fileName, fileId);
|
|
@@ -45,17 +55,20 @@ export const FileDetailsLabel = ({
|
|
|
45
55
|
type="button"
|
|
46
56
|
variant="plain"
|
|
47
57
|
aria-label={closeButtonAriaLabel ?? `Close ${fileName}`}
|
|
48
|
-
icon={
|
|
58
|
+
icon={closeButtonIcon}
|
|
49
59
|
onClick={handleClose}
|
|
50
60
|
/>
|
|
51
61
|
}
|
|
52
62
|
{...(onClick && { onClick: (event) => onClick(event, fileName, fileId) })}
|
|
63
|
+
{...props}
|
|
53
64
|
>
|
|
54
65
|
<div className="pf-chatbot__file-label-contents">
|
|
55
66
|
<FileDetails
|
|
56
67
|
className={isLoading ? 'pf-chatbot__file-label-loading' : undefined}
|
|
57
68
|
fileName={fileName}
|
|
58
69
|
languageTestId={languageTestId}
|
|
70
|
+
fileSize={fileSize}
|
|
71
|
+
hasTruncation={hasTruncation}
|
|
59
72
|
/>
|
|
60
73
|
{isLoading && <Spinner data-testid={spinnerTestId} size="sm" />}
|
|
61
74
|
</div>
|
|
@@ -21,6 +21,7 @@ exports[`FileDetailsLabel should render file details label 1`] = `
|
|
|
21
21
|
class="pf-v6-l-flex pf-m-align-items-center pf-m-align-self-center pf-m-justify-content-center pf-chatbot__code-icon"
|
|
22
22
|
>
|
|
23
23
|
<svg
|
|
24
|
+
aria-hidden="true"
|
|
24
25
|
fill="currentColor"
|
|
25
26
|
height="24"
|
|
26
27
|
viewBox="0 0 24 24"
|
|
@@ -52,30 +53,38 @@ exports[`FileDetailsLabel should render file details label 1`] = `
|
|
|
52
53
|
</svg>
|
|
53
54
|
</div>
|
|
54
55
|
<div
|
|
55
|
-
class="pf-v6-l-
|
|
56
|
+
class="pf-v6-l-flex pf-m-gap-xs"
|
|
56
57
|
>
|
|
57
58
|
<div
|
|
58
|
-
class="
|
|
59
|
+
class=""
|
|
59
60
|
>
|
|
60
|
-
<
|
|
61
|
-
class="pf-
|
|
61
|
+
<div
|
|
62
|
+
class="pf-v6-l-flex pf-m-column pf-m-gap-none"
|
|
62
63
|
>
|
|
63
|
-
<
|
|
64
|
-
class="
|
|
65
|
-
tabindex="0"
|
|
64
|
+
<div
|
|
65
|
+
class=""
|
|
66
66
|
>
|
|
67
67
|
<span
|
|
68
|
-
class="pf-
|
|
68
|
+
class="pf-chatbot__code-fileName"
|
|
69
69
|
>
|
|
70
|
-
|
|
70
|
+
<span
|
|
71
|
+
class="pf-v6-c-truncate"
|
|
72
|
+
tabindex="0"
|
|
73
|
+
>
|
|
74
|
+
<span
|
|
75
|
+
class="pf-v6-c-truncate__start"
|
|
76
|
+
>
|
|
77
|
+
test
|
|
78
|
+
</span>
|
|
79
|
+
</span>
|
|
71
80
|
</span>
|
|
72
|
-
</
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
</div>
|
|
82
|
+
<div
|
|
83
|
+
class="pf-chatbot__code-language"
|
|
84
|
+
>
|
|
85
|
+
TEXT
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
79
88
|
</div>
|
|
80
89
|
</div>
|
|
81
90
|
</div>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
.pf-chatbot__image-preview-body {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: var(--pf-t--global--spacer--lg);
|
|
5
|
+
--pf-v6-c-label--MaxWidth: initial;
|
|
6
|
+
--pf-v6-c-modal-box--ZIndex: var(--pf-t--global--z-index--2xl);
|
|
7
|
+
|
|
8
|
+
img {
|
|
9
|
+
flex: 1 0 0;
|
|
10
|
+
align-self: stretch;
|
|
11
|
+
}
|
|
12
|
+
.pf-chatbot__file-label {
|
|
13
|
+
min-width: fit-content;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.pf-chatbot__image-preview-stack {
|
|
18
|
+
height: unset;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.pf-v6-c-modal-box__footer.pf-chatbot__image-preview-footer {
|
|
22
|
+
padding-block-start: var(--pf-t--global--spacer--sm);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.pf-chatbot__image-preview-footer-buttons {
|
|
26
|
+
display: flex;
|
|
27
|
+
gap: var(--pf-t--global--spacer--xs);
|
|
28
|
+
align-items: center;
|
|
29
|
+
justify-content: space-between;
|
|
30
|
+
flex: 1;
|
|
31
|
+
|
|
32
|
+
.pf-v6-c-button {
|
|
33
|
+
border-radius: var(--pf-t--global--border--radius--pill);
|
|
34
|
+
padding: var(--pf-t--global--spacer--sm);
|
|
35
|
+
width: 2.31rem;
|
|
36
|
+
height: 2.31rem;
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: center;
|
|
40
|
+
}
|
|
41
|
+
button:disabled,
|
|
42
|
+
button[disabled] {
|
|
43
|
+
.pf-v6-c-icon__content {
|
|
44
|
+
color: var(--pf-t--global--icon--color--disabled);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
.pf-v6-c-button__text {
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
}
|
|
51
|
+
// Interactive states
|
|
52
|
+
.pf-v6-c-button:hover,
|
|
53
|
+
.pf-v6-c-button:focus {
|
|
54
|
+
.pf-v6-c-button__icon {
|
|
55
|
+
color: var(--pf-t--global--icon--color--regular);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
.pf-v6-c-button__icon {
|
|
59
|
+
color: var(--pf-t--global--icon--color--subtle);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import ImagePreview from './ImagePreview';
|
|
5
|
+
import { ChatbotDisplayMode } from '../Chatbot';
|
|
6
|
+
|
|
7
|
+
const mockImages = [
|
|
8
|
+
{
|
|
9
|
+
fileName: 'image1.jpg',
|
|
10
|
+
fileSize: '2.5 MB',
|
|
11
|
+
image: <img src="" alt="Test image 1" />
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
fileName: 'image2.png',
|
|
15
|
+
fileSize: '1.8 MB',
|
|
16
|
+
image: <img src="" alt="Test image 2" />
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
fileName: 'image3.gif',
|
|
20
|
+
image: <img src="" alt="Test image 3" />
|
|
21
|
+
}
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const defaultProps = {
|
|
25
|
+
isModalOpen: true,
|
|
26
|
+
handleModalToggle: jest.fn(),
|
|
27
|
+
images: mockImages
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
describe('ImagePreview', () => {
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
jest.clearAllMocks();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('renders modal when isModalOpen is true', () => {
|
|
36
|
+
render(<ImagePreview {...defaultProps} />);
|
|
37
|
+
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('does not render modal when isModalOpen is false', () => {
|
|
41
|
+
render(<ImagePreview {...defaultProps} isModalOpen={false} />);
|
|
42
|
+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('displays custom title when provided', () => {
|
|
46
|
+
const customTitle = 'Custom image preview';
|
|
47
|
+
render(<ImagePreview {...defaultProps} title={customTitle} />);
|
|
48
|
+
expect(screen.getByRole('heading', { name: customTitle })).toBeInTheDocument();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('displays default title when no title provided', () => {
|
|
52
|
+
render(<ImagePreview {...defaultProps} />);
|
|
53
|
+
expect(screen.getByRole('heading', { name: /Preview images/i })).toBeInTheDocument();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('calls handleModalToggle when modal is closed', () => {
|
|
57
|
+
const mockHandleToggle = jest.fn();
|
|
58
|
+
render(<ImagePreview {...defaultProps} handleModalToggle={mockHandleToggle} />);
|
|
59
|
+
const closeButton = screen.getByRole('button', { name: /close/i });
|
|
60
|
+
fireEvent.click(closeButton);
|
|
61
|
+
expect(mockHandleToggle).toHaveBeenCalledTimes(1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('displays first image by default', () => {
|
|
65
|
+
render(<ImagePreview {...defaultProps} />);
|
|
66
|
+
expect(screen.getByText('image1.jpg')).toBeInTheDocument();
|
|
67
|
+
expect(screen.getByText('2.5 MB')).toBeInTheDocument();
|
|
68
|
+
expect(screen.getByAltText('Test image 1')).toBeInTheDocument();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('displays page counter correctly', () => {
|
|
72
|
+
render(<ImagePreview {...defaultProps} />);
|
|
73
|
+
expect(screen.getByText('1/3')).toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('navigates to next image when next button is clicked', () => {
|
|
77
|
+
const mockOnNextClick = jest.fn();
|
|
78
|
+
render(<ImagePreview {...defaultProps} onNextClick={mockOnNextClick} />);
|
|
79
|
+
const nextButton = screen.getByRole('button', { name: /Go to next image/i });
|
|
80
|
+
fireEvent.click(nextButton);
|
|
81
|
+
expect(mockOnNextClick).toHaveBeenCalled();
|
|
82
|
+
expect(screen.getByText('2/3')).toBeInTheDocument();
|
|
83
|
+
expect(screen.getByText('image2.png')).toBeInTheDocument();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('navigates to previous image when previous button is clicked', () => {
|
|
87
|
+
const mockOnPreviousClick = jest.fn();
|
|
88
|
+
render(<ImagePreview {...defaultProps} onPreviousClick={mockOnPreviousClick} />);
|
|
89
|
+
// First go to page 2
|
|
90
|
+
const nextButton = screen.getByRole('button', { name: /Go to next image/i });
|
|
91
|
+
fireEvent.click(nextButton);
|
|
92
|
+
// Then go back to page 1
|
|
93
|
+
const previousButton = screen.getByRole('button', { name: /Go to previous image/i });
|
|
94
|
+
fireEvent.click(previousButton);
|
|
95
|
+
expect(mockOnPreviousClick).toHaveBeenCalled();
|
|
96
|
+
expect(screen.getByText('1/3')).toBeInTheDocument();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('calls onSetPage when page changes', () => {
|
|
100
|
+
const mockOnSetPage = jest.fn();
|
|
101
|
+
render(<ImagePreview {...defaultProps} onSetPage={mockOnSetPage} />);
|
|
102
|
+
const nextButton = screen.getByRole('button', { name: /Go to next image/i });
|
|
103
|
+
fireEvent.click(nextButton);
|
|
104
|
+
expect(mockOnSetPage).toHaveBeenCalledWith(expect.any(Object), 2);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('disables previous button on first page', () => {
|
|
108
|
+
render(<ImagePreview {...defaultProps} />);
|
|
109
|
+
const previousButton = screen.getByRole('button', { name: /Go to previous image/i });
|
|
110
|
+
expect(previousButton).toBeDisabled();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('disables next button on last page', () => {
|
|
114
|
+
render(<ImagePreview {...defaultProps} />);
|
|
115
|
+
// Navigate to last page
|
|
116
|
+
const nextButton = screen.getByRole('button', { name: /Go to next image/i });
|
|
117
|
+
fireEvent.click(nextButton); // page 2
|
|
118
|
+
fireEvent.click(nextButton); // page 3
|
|
119
|
+
expect(nextButton).toBeDisabled();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('disables both navigation buttons when isDisabled is true', () => {
|
|
123
|
+
render(<ImagePreview {...defaultProps} isDisabled={true} />);
|
|
124
|
+
const previousButton = screen.getByRole('button', { name: /Go to previous image/i });
|
|
125
|
+
const nextButton = screen.getByRole('button', { name: /Go to next image/i });
|
|
126
|
+
expect(previousButton).toBeDisabled();
|
|
127
|
+
expect(nextButton).toBeDisabled();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('uses custom aria labels for pagination', () => {
|
|
131
|
+
const customLabels = {
|
|
132
|
+
paginationAriaLabel: 'Custom pagination',
|
|
133
|
+
toPreviousPageAriaLabel: 'Go to previous image',
|
|
134
|
+
toNextPageAriaLabel: 'Go to next image'
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
render(<ImagePreview {...defaultProps} {...customLabels} />);
|
|
138
|
+
expect(screen.getByRole('navigation', { name: 'Custom pagination' })).toBeInTheDocument();
|
|
139
|
+
expect(screen.getByRole('button', { name: 'Go to previous image' })).toBeInTheDocument();
|
|
140
|
+
expect(screen.getByRole('button', { name: 'Go to next image' })).toBeInTheDocument();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('renders with compact mode when isCompact is true', () => {
|
|
144
|
+
render(<ImagePreview {...defaultProps} isCompact={true} />);
|
|
145
|
+
const modal = screen.getByRole('dialog');
|
|
146
|
+
expect(modal).toHaveClass('pf-m-compact');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('applies custom className when provided', () => {
|
|
150
|
+
const customClassName = 'custom-image-preview';
|
|
151
|
+
render(<ImagePreview {...defaultProps} className={customClassName} />);
|
|
152
|
+
const modal = screen.getByRole('dialog');
|
|
153
|
+
expect(modal).toHaveClass(customClassName);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('applies display mode class correctly', () => {
|
|
157
|
+
render(<ImagePreview {...defaultProps} displayMode={ChatbotDisplayMode.embedded} />);
|
|
158
|
+
const modal = screen.getByRole('dialog');
|
|
159
|
+
expect(modal).toHaveClass('pf-chatbot__image-preview-modal--embedded');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('passes additional props to ChatbotModal', () => {
|
|
163
|
+
const modalClass = 'custom-modal-class';
|
|
164
|
+
const additionalProps = {
|
|
165
|
+
'data-testid': 'modal',
|
|
166
|
+
className: modalClass
|
|
167
|
+
};
|
|
168
|
+
render(<ImagePreview {...defaultProps} {...additionalProps} />);
|
|
169
|
+
const modal = screen.getByTestId('modal');
|
|
170
|
+
expect(modal).toBeInTheDocument();
|
|
171
|
+
expect(modal).toBeInTheDocument();
|
|
172
|
+
expect(modal).toHaveClass(modalClass);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('passes modalHeaderProps correctly', () => {
|
|
176
|
+
const headerClass = 'custom-modal-header-class';
|
|
177
|
+
const headerProps = {
|
|
178
|
+
'data-testid': 'header',
|
|
179
|
+
className: headerClass
|
|
180
|
+
};
|
|
181
|
+
render(<ImagePreview {...defaultProps} modalHeaderProps={headerProps} />);
|
|
182
|
+
expect(screen.getByTestId('header')).toBeInTheDocument();
|
|
183
|
+
expect(screen.getByTestId('header')).toHaveClass(headerClass);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('passes modalBodyProps correctly', () => {
|
|
187
|
+
const bodyClass = 'custom-modal-body-class';
|
|
188
|
+
const bodyProps = {
|
|
189
|
+
'data-testid': 'body',
|
|
190
|
+
className: bodyClass
|
|
191
|
+
};
|
|
192
|
+
render(<ImagePreview {...defaultProps} modalBodyProps={bodyProps} />);
|
|
193
|
+
expect(screen.getByTestId('body')).toBeInTheDocument();
|
|
194
|
+
expect(screen.getByTestId('body')).toHaveClass(bodyClass);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('handles single image without pagination', () => {
|
|
198
|
+
const singleImage = [mockImages[0]];
|
|
199
|
+
render(<ImagePreview {...defaultProps} images={singleImage} />);
|
|
200
|
+
expect(screen.queryByText('1/1')).not.toBeInTheDocument();
|
|
201
|
+
expect(screen.queryByRole('button', { name: /Go to previous image/i })).not.toBeInTheDocument();
|
|
202
|
+
expect(screen.queryByRole('button', { name: /Go to next image/i })).not.toBeInTheDocument();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('calls onCloseFileDetailsLabel when file details close button is clicked', () => {
|
|
206
|
+
const mockOnClose = jest.fn();
|
|
207
|
+
render(<ImagePreview {...defaultProps} onCloseFileDetailsLabel={mockOnClose} />);
|
|
208
|
+
const closeButton = screen.getByRole('button', { name: /Close image1.jpg/i });
|
|
209
|
+
fireEvent.click(closeButton);
|
|
210
|
+
expect(mockOnClose).toHaveBeenCalled();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('passes fileDetailsLabelProps correctly to FileDetailsLabel', () => {
|
|
214
|
+
const customFileDetailsProps = {
|
|
215
|
+
'data-testid': 'custom-file-details'
|
|
216
|
+
};
|
|
217
|
+
render(<ImagePreview {...defaultProps} fileDetailsLabelProps={customFileDetailsProps as any} />);
|
|
218
|
+
expect(screen.getByTestId('custom-file-details')).toBeInTheDocument();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('displays file details for current page when navigating', () => {
|
|
222
|
+
render(<ImagePreview {...defaultProps} />);
|
|
223
|
+
// Initially shows first image details
|
|
224
|
+
expect(screen.getByText('image1.jpg')).toBeInTheDocument();
|
|
225
|
+
expect(screen.getByText('2.5 MB')).toBeInTheDocument();
|
|
226
|
+
|
|
227
|
+
// Navigate to second page
|
|
228
|
+
const nextButton = screen.getByRole('button', { name: /Go to next image/i });
|
|
229
|
+
fireEvent.click(nextButton);
|
|
230
|
+
|
|
231
|
+
// Should now show second image details
|
|
232
|
+
expect(screen.getByText('image2.png')).toBeInTheDocument();
|
|
233
|
+
expect(screen.getByText('1.8 MB')).toBeInTheDocument();
|
|
234
|
+
|
|
235
|
+
// Navigate to third page
|
|
236
|
+
fireEvent.click(nextButton);
|
|
237
|
+
|
|
238
|
+
// Should now show third image details (no file size)
|
|
239
|
+
expect(screen.getByText('image3.gif')).toBeInTheDocument();
|
|
240
|
+
expect(screen.queryByText(/MB/)).not.toBeInTheDocument();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('sets hasTruncation to false on FileDetailsLabel', () => {
|
|
244
|
+
const longFileName = 'very-long-filename-that-would-normally-be-truncated-in-other-contexts.jpg';
|
|
245
|
+
const imageWithLongName = {
|
|
246
|
+
fileName: longFileName,
|
|
247
|
+
fileSize: '1.0 MB',
|
|
248
|
+
image: <img src="" alt="Test image with long name" />
|
|
249
|
+
};
|
|
250
|
+
render(<ImagePreview {...defaultProps} images={[imageWithLongName]} />);
|
|
251
|
+
expect(screen.getByText(longFileName)).toBeInTheDocument();
|
|
252
|
+
});
|
|
253
|
+
});
|