diff --git a/packages/module/src/Message/CodeBlockMessage/CodeBlockMessage.test.tsx b/packages/module/src/Message/CodeBlockMessage/CodeBlockMessage.test.tsx
new file mode 100644
index 000000000..a920df3a0
--- /dev/null
+++ b/packages/module/src/Message/CodeBlockMessage/CodeBlockMessage.test.tsx
@@ -0,0 +1,171 @@
+import '@testing-library/jest-dom';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import CodeBlockMessage from './CodeBlockMessage';
+
+// Mock clipboard API
+Object.assign(navigator, {
+ clipboard: {
+ writeText: jest.fn()
+ }
+});
+
+describe('CodeBlockMessage', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should render inline code for single-line content', () => {
+ render(const x = 5;);
+ const code = screen.getByText('const x = 5;');
+ expect(code.tagName).toBe('CODE');
+ expect(code).toHaveClass('pf-chatbot__message-inline-code');
+ });
+
+ it('should render code block for multi-line content', () => {
+ const multilineCode = 'const x = 5;\nconst y = 10;';
+ const { container } = render({multilineCode});
+ const codeElement = container.querySelector('code');
+ expect(codeElement?.textContent).toBe(multilineCode);
+ });
+
+ it('should display language label', () => {
+ const code = 'const x = 5;\nconst y = 10;';
+ render({code});
+ expect(screen.getByText('javascript')).toBeInTheDocument();
+ });
+
+ it('should render copy button', () => {
+ const code = 'const x = 5;\nconst y = 10;';
+ render({code});
+ expect(screen.getByRole('button', { name: 'Copy code' })).toBeInTheDocument();
+ });
+
+ it('should copy plain string content to clipboard', async () => {
+ const code = 'const x = 5;\nconst y = 10;';
+ render({code});
+
+ const copyButton = screen.getByRole('button', { name: 'Copy code' });
+ await userEvent.click(copyButton);
+
+ expect(navigator.clipboard.writeText).toHaveBeenCalledWith(code);
+ });
+
+ it('should extract text content from React elements when copying', async () => {
+ // Simulate what happens with syntax highlighting - children become React elements
+ const { container } = render(
+
+ const x = 5;{'\n'}
+ const y = 10;
+
+ );
+
+ const copyButton = screen.getByRole('button', { name: 'Copy code' });
+ await userEvent.click(copyButton);
+
+ // Should extract actual text content from DOM, not "[object Object]"
+ const codeElement = container.querySelector('code');
+ const expectedText = codeElement?.textContent || '';
+ expect(navigator.clipboard.writeText).toHaveBeenCalledWith(expectedText);
+ expect(expectedText).not.toContain('[object Object]');
+ });
+
+ it('should show check icon after copying', async () => {
+ const code = 'const x = 5;\nconst y = 10;';
+ render({code});
+
+ const copyButton = screen.getByRole('button', { name: 'Copy code' });
+ await userEvent.click(copyButton);
+
+ // Check icon should be visible (we can verify by checking if CopyIcon is not present)
+ const svgElement = copyButton.querySelector('svg');
+ expect(svgElement).toBeInTheDocument();
+ });
+
+ it('should render expandable section when isExpandable is true', () => {
+ const code = 'const x = 5;\nconst y = 10;';
+ render({code});
+
+ expect(screen.getByRole('button', { name: 'Show more' })).toBeInTheDocument();
+ });
+
+ it('should toggle expandable section', async () => {
+ const code = 'const x = 5;\nconst y = 10;';
+ render({code});
+
+ const toggleButton = screen.getByRole('button', { name: 'Show more' });
+ await userEvent.click(toggleButton);
+
+ expect(screen.getByRole('button', { name: 'Show less' })).toBeInTheDocument();
+ });
+
+ it('should use custom expanded/collapsed text', () => {
+ const code = 'const x = 5;\nconst y = 10;';
+ render(
+
+ {code}
+
+ );
+
+ expect(screen.getByRole('button', { name: 'Reveal' })).toBeInTheDocument();
+ });
+
+ it('should pass through expandableSectionProps', () => {
+ const code = 'const x = 5;\nconst y = 10;';
+ const { container } = render(
+
+ {code}
+
+ );
+
+ const expandableSection = container.querySelector('.pf-v6-c-expandable-section.custom-expandable-class');
+ expect(expandableSection).toBeInTheDocument();
+ });
+
+ it('should render custom actions', () => {
+ const code = 'const x = 5;\nconst y = 10;';
+ const customAction = ;
+ render({code});
+
+ expect(screen.getByRole('button', { name: 'Custom action' })).toBeInTheDocument();
+ });
+
+ it('should apply isPrimary class to inline code', () => {
+ render(const x = 5;);
+ const code = screen.getByText('const x = 5;');
+ expect(code).toHaveClass('pf-m-primary');
+ });
+
+ it('should apply shouldRetainStyles class to code block', () => {
+ const code = 'const x = 5;\nconst y = 10;';
+ const { container } = render({code});
+
+ const codeBlockDiv = container.querySelector('.pf-chatbot__message-code-block');
+ expect(codeBlockDiv).toHaveClass('pf-m-markdown');
+ });
+
+ it('should use custom aria-label for copy button', () => {
+ const code = 'const x = 5;\nconst y = 10;';
+ render({code});
+
+ expect(screen.getByRole('button', { name: 'Copy this code' })).toBeInTheDocument();
+ });
+
+ it('should prioritize data-expanded-text over expandedText prop', () => {
+ const code = 'const x = 5;\nconst y = 10;';
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(jest.fn());
+
+ render(
+
+ {code}
+
+ );
+
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
+ 'Message:',
+ expect.stringContaining('data-expanded-text or data-collapsed-text will override')
+ );
+
+ consoleErrorSpy.mockRestore();
+ });
+});
diff --git a/packages/module/src/Message/CodeBlockMessage/CodeBlockMessage.tsx b/packages/module/src/Message/CodeBlockMessage/CodeBlockMessage.tsx
index 1e051ff42..ca38e2e12 100644
--- a/packages/module/src/Message/CodeBlockMessage/CodeBlockMessage.tsx
+++ b/packages/module/src/Message/CodeBlockMessage/CodeBlockMessage.tsx
@@ -92,13 +92,22 @@ const CodeBlockMessage = ({
);
}
- const onToggle = (isExpanded) => {
+ const onToggle = (isExpanded: boolean) => {
setIsExpanded(isExpanded);
};
// Handle clicking copy button
- const handleCopy = useCallback((event, text) => {
- navigator.clipboard.writeText(text.toString());
+ const handleCopy = useCallback((_event: React.MouseEvent, text: React.ReactNode) => {
+ let textToCopy = '';
+ if (typeof text === 'string') {
+ textToCopy = text;
+ } else {
+ if (codeBlockRef.current) {
+ const codeElement = codeBlockRef.current.querySelector('code');
+ textToCopy = codeElement?.textContent || '';
+ }
+ }
+ navigator.clipboard.writeText(textToCopy);
setCopied(true);
}, []);