{
setDraftMessage: jest.fn(),
draftFileContents: [],
setDraftFileContents: jest.fn(),
+ isSettingsDropdownOpen: false,
+ setIsSettingsDropdownOpen: jest.fn(),
});
localStorage.clear();
@@ -508,6 +510,8 @@ describe('LightspeedChat', () => {
setDraftMessage: jest.fn(),
draftFileContents: [],
setDraftFileContents: jest.fn(),
+ isSettingsDropdownOpen: false,
+ setIsSettingsDropdownOpen: jest.fn(),
});
render(setupLightspeedChat());
@@ -539,6 +543,8 @@ describe('LightspeedChat', () => {
setDraftMessage: jest.fn(),
draftFileContents: [],
setDraftFileContents: jest.fn(),
+ isSettingsDropdownOpen: false,
+ setIsSettingsDropdownOpen: jest.fn(),
});
render(setupLightspeedChat());
@@ -570,6 +576,8 @@ describe('LightspeedChat', () => {
setDraftMessage: jest.fn(),
draftFileContents: [],
setDraftFileContents: jest.fn(),
+ isSettingsDropdownOpen: false,
+ setIsSettingsDropdownOpen: jest.fn(),
});
render(setupLightspeedChat());
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/__tests__/LightspeedDrawerProvider.test.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/__tests__/LightspeedDrawerProvider.test.tsx
new file mode 100644
index 0000000000..96a60d3a88
--- /dev/null
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/__tests__/LightspeedDrawerProvider.test.tsx
@@ -0,0 +1,253 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { useContext } from 'react';
+import { MemoryRouter } from 'react-router-dom';
+
+import { ChatbotDisplayMode } from '@patternfly/chatbot';
+import { act, fireEvent, render, screen } from '@testing-library/react';
+
+import { LightspeedDrawerContext } from '../LightspeedDrawerContext';
+import { LightspeedDrawerProvider } from '../LightspeedDrawerProvider';
+
+// Mock the LightspeedChatContainer to avoid complex dependencies
+jest.mock('../LightspeedChatContainer', () => ({
+ LightspeedChatContainer: () => (
+ Chat Container
+ ),
+}));
+
+// Test component to access and display context values
+const ContextConsumer = () => {
+ const context = useContext(LightspeedDrawerContext);
+ if (!context) return null;
+
+ return (
+
+ {context.displayMode}
+
+ {context.isChatbotActive.toString()}
+
+
+ {context.isSettingsDropdownOpen.toString()}
+
+
+
+
+
+
+
+
+ );
+};
+
+const renderWithRouter = (initialEntries: string[] = ['/']) => {
+ return render(
+
+
+
+
+ ,
+ );
+};
+
+describe('LightspeedDrawerProvider', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('Escape key display mode cycling', () => {
+ it('should cycle from fullscreen to docked when Escape is pressed', async () => {
+ renderWithRouter(['/lightspeed']);
+
+ // Verify we start in fullscreen mode (embedded) on lightspeed route
+ expect(screen.getByTestId('display-mode').textContent).toBe(
+ ChatbotDisplayMode.embedded,
+ );
+
+ // Press Escape
+ await act(async () => {
+ fireEvent.keyDown(document, { key: 'Escape' });
+ });
+
+ // Should now be in docked mode
+ expect(screen.getByTestId('display-mode').textContent).toBe(
+ ChatbotDisplayMode.docked,
+ );
+ });
+
+ it('should cycle from docked to overlay when Escape is pressed', async () => {
+ renderWithRouter();
+
+ // Open chatbot first
+ await act(async () => {
+ fireEvent.click(screen.getByTestId('toggle-chatbot'));
+ });
+
+ // Set to docked mode
+ await act(async () => {
+ fireEvent.click(screen.getByTestId('set-display-mode-docked'));
+ });
+
+ expect(screen.getByTestId('display-mode').textContent).toBe(
+ ChatbotDisplayMode.docked,
+ );
+
+ // Press Escape
+ await act(async () => {
+ fireEvent.keyDown(document, { key: 'Escape' });
+ });
+
+ // Should now be in overlay mode
+ expect(screen.getByTestId('display-mode').textContent).toBe(
+ ChatbotDisplayMode.default,
+ );
+ });
+
+ it('should close chatbot when Escape is pressed in overlay mode', async () => {
+ renderWithRouter();
+
+ // Open chatbot
+ await act(async () => {
+ fireEvent.click(screen.getByTestId('toggle-chatbot'));
+ });
+
+ expect(screen.getByTestId('is-chatbot-active').textContent).toBe('true');
+ expect(screen.getByTestId('display-mode').textContent).toBe(
+ ChatbotDisplayMode.default,
+ );
+
+ // Press Escape
+ await act(async () => {
+ fireEvent.keyDown(document, { key: 'Escape' });
+ });
+
+ // Chatbot should be closed
+ expect(screen.getByTestId('is-chatbot-active').textContent).toBe('false');
+ });
+
+ it('should not cycle display mode when settings dropdown is open', async () => {
+ renderWithRouter();
+
+ // Open chatbot
+ await act(async () => {
+ fireEvent.click(screen.getByTestId('toggle-chatbot'));
+ });
+
+ // Set to docked mode
+ await act(async () => {
+ fireEvent.click(screen.getByTestId('set-display-mode-docked'));
+ });
+
+ // Open settings dropdown
+ await act(async () => {
+ fireEvent.click(screen.getByTestId('set-settings-dropdown-open'));
+ });
+
+ expect(screen.getByTestId('is-settings-dropdown-open').textContent).toBe(
+ 'true',
+ );
+
+ // Press Escape - should not cycle mode because dropdown is open
+ await act(async () => {
+ fireEvent.keyDown(document, { key: 'Escape' });
+ });
+
+ // Should still be in docked mode
+ expect(screen.getByTestId('display-mode').textContent).toBe(
+ ChatbotDisplayMode.docked,
+ );
+ });
+
+ it('should not respond to Escape when chatbot is closed', async () => {
+ renderWithRouter();
+
+ // Chatbot should be closed initially
+ expect(screen.getByTestId('is-chatbot-active').textContent).toBe('false');
+
+ const initialDisplayMode = screen.getByTestId('display-mode').textContent;
+
+ // Press Escape
+ await act(async () => {
+ fireEvent.keyDown(document, { key: 'Escape' });
+ });
+
+ // Display mode should remain unchanged
+ expect(screen.getByTestId('display-mode').textContent).toBe(
+ initialDisplayMode,
+ );
+ });
+ });
+
+ describe('isSettingsDropdownOpen state', () => {
+ it('should track settings dropdown open state', async () => {
+ renderWithRouter();
+
+ // Initially should be false
+ expect(screen.getByTestId('is-settings-dropdown-open').textContent).toBe(
+ 'false',
+ );
+
+ // Open settings dropdown
+ await act(async () => {
+ fireEvent.click(screen.getByTestId('set-settings-dropdown-open'));
+ });
+
+ expect(screen.getByTestId('is-settings-dropdown-open').textContent).toBe(
+ 'true',
+ );
+
+ // Close settings dropdown
+ await act(async () => {
+ fireEvent.click(screen.getByTestId('set-settings-dropdown-closed'));
+ });
+
+ expect(screen.getByTestId('is-settings-dropdown-open').textContent).toBe(
+ 'false',
+ );
+ });
+ });
+});
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/__tests__/LightspeedDrawerStateExposer.test.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/__tests__/LightspeedDrawerStateExposer.test.tsx
index 08c0bbc182..85d34d8423 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/__tests__/LightspeedDrawerStateExposer.test.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/__tests__/LightspeedDrawerStateExposer.test.tsx
@@ -41,6 +41,8 @@ describe('LightspeedDrawerStateExposer', () => {
setDraftMessage: jest.fn(),
draftFileContents: [],
setDraftFileContents: jest.fn(),
+ isSettingsDropdownOpen: false,
+ setIsSettingsDropdownOpen: jest.fn(),
...overrides,
});
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/__tests__/LightspeedFAB.test.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/__tests__/LightspeedFAB.test.tsx
index a59bd76b20..6ab450c81c 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/components/__tests__/LightspeedFAB.test.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/components/__tests__/LightspeedFAB.test.tsx
@@ -41,6 +41,8 @@ describe('LightspeedFAB', () => {
setDraftMessage: jest.fn(),
draftFileContents: [],
setDraftFileContents: jest.fn(),
+ isSettingsDropdownOpen: false,
+ setIsSettingsDropdownOpen: jest.fn(),
...overrides,
});
diff --git a/workspaces/lightspeed/plugins/lightspeed/src/hooks/__tests__/useLightspeedDrawerContext.test.tsx b/workspaces/lightspeed/plugins/lightspeed/src/hooks/__tests__/useLightspeedDrawerContext.test.tsx
index 2eb718e202..85b524418e 100644
--- a/workspaces/lightspeed/plugins/lightspeed/src/hooks/__tests__/useLightspeedDrawerContext.test.tsx
+++ b/workspaces/lightspeed/plugins/lightspeed/src/hooks/__tests__/useLightspeedDrawerContext.test.tsx
@@ -34,6 +34,8 @@ describe('useLightspeedDrawerContext', () => {
setDraftMessage: jest.fn(),
draftFileContents: [],
setDraftFileContents: jest.fn(),
+ isSettingsDropdownOpen: false,
+ setIsSettingsDropdownOpen: jest.fn(),
};
it('should return context value when used within provider', () => {
@@ -258,4 +260,41 @@ describe('useLightspeedDrawerContext', () => {
result.current.setDraftMessage('new draft message');
expect(mockSetDraftMessage).toHaveBeenCalledWith('new draft message');
});
+
+ it('should return isSettingsDropdownOpen from context', () => {
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
+
+ {children}
+
+ );
+
+ const { result } = renderHook(() => useLightspeedDrawerContext(), {
+ wrapper,
+ });
+
+ expect(result.current.isSettingsDropdownOpen).toBe(true);
+ });
+
+ it('should provide working setIsSettingsDropdownOpen function', () => {
+ const mockSetIsSettingsDropdownOpen = jest.fn();
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
+
+ {children}
+
+ );
+
+ const { result } = renderHook(() => useLightspeedDrawerContext(), {
+ wrapper,
+ });
+
+ result.current.setIsSettingsDropdownOpen(true);
+ expect(mockSetIsSettingsDropdownOpen).toHaveBeenCalledWith(true);
+ });
});