Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: update conversation data after edit #48

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions public/components/__tests__/chat_window_header_title.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { act, fireEvent, render, waitFor } from '@testing-library/react';
import { BehaviorSubject } from 'rxjs';
import { I18nProvider } from '@osd/i18n/react';

import { coreMock } from '../../../../../src/core/public/mocks';
import * as useChatStateExports from '../../hooks/use_chat_state';
import * as useChatActionsExports from '../../hooks/use_chat_actions';
import * as useSaveChatExports from '../../hooks/use_save_chat';
import * as chatContextExports from '../../contexts/chat_context';
import * as coreContextExports from '../../contexts/core_context';

import { ChatWindowHeaderTitle } from '../chat_window_header_title';

const setup = () => {
const useCoreMock = {
services: {
...coreMock.createStart(),
sessions: {
sessions$: new BehaviorSubject({
objects: [
{
id: '1',
title: 'foo',
},
],
total: 1,
}),
reload: jest.fn(),
},
},
};
useCoreMock.services.http.put.mockImplementation(() => Promise.resolve());

const useChatStateMock = {
chatState: { messages: [] },
};
const useChatContextMock = {
sessionId: '1',
title: 'foo',
setSessionId: jest.fn(),
setTitle: jest.fn(),
};
const useChatActionsMock = {
loadChat: jest.fn(),
};
const useSaveChatMock = {
saveChat: jest.fn(),
};
jest.spyOn(coreContextExports, 'useCore').mockReturnValue(useCoreMock);
jest.spyOn(useChatStateExports, 'useChatState').mockReturnValue(useChatStateMock);
jest.spyOn(chatContextExports, 'useChatContext').mockReturnValue(useChatContextMock);
jest.spyOn(useChatActionsExports, 'useChatActions').mockReturnValue(useChatActionsMock);
jest.spyOn(useSaveChatExports, 'useSaveChat').mockReturnValue(useSaveChatMock);

const renderResult = render(
<I18nProvider>
<ChatWindowHeaderTitle />
</I18nProvider>
);

return {
useCoreMock,
useChatStateMock,
useChatContextMock,
renderResult,
};
};

describe('<ChatWindowHeaderTitle />', () => {
it('should reload history list after edit conversation name', async () => {
const { renderResult, useCoreMock } = setup();

act(() => {
fireEvent.click(renderResult.getByText('foo'));
});

act(() => {
fireEvent.click(renderResult.getByText('Rename conversation'));
});

act(() => {
fireEvent.change(renderResult.getByLabelText('Conversation name input'), {
target: { value: 'bar' },
});
});

expect(useCoreMock.services.sessions.reload).not.toHaveBeenCalled();

act(() => {
fireEvent.click(renderResult.getByTestId('confirmModalConfirmButton'));
});

waitFor(() => {
expect(useCoreMock.services.sessions.reload).toHaveBeenCalled();
});
});
});
6 changes: 5 additions & 1 deletion public/components/chat_window_header_title.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,14 @@ export const ChatWindowHeaderTitle = React.memo(() => {
(status: 'updated' | string, newTitle?: string) => {
if (status === 'updated') {
chatContext.setTitle(newTitle);
const sessions = core.services.sessions.sessions$.getValue();
if (sessions?.objects.find((session) => session.id === chatContext.sessionId)) {
core.services.sessions.reload();
}
}
setRenameModalOpen(false);
},
[chatContext]
[chatContext, core.services.sessions]
);

const button = (
Expand Down
6 changes: 5 additions & 1 deletion public/components/edit_conversation_name_modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ export const EditConversationNameModal = ({
<p>Please enter a new name for your conversation.</p>
</EuiText>
<EuiSpacer size="xs" />
<EuiFieldText inputRef={titleInputRef} defaultValue={defaultTitle} />
<EuiFieldText
inputRef={titleInputRef}
defaultValue={defaultTitle}
aria-label="Conversation name input"
/>
</EuiConfirmModal>
);
};
71 changes: 71 additions & 0 deletions public/tabs/history/__tests__/chat_history_search_list.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { act, fireEvent, render, waitFor } from '@testing-library/react';
import { I18nProvider } from '@osd/i18n/react';

import { coreMock } from '../../../../../../src/core/public/mocks';
import * as chatContextExports from '../../../contexts/chat_context';
import * as coreContextExports from '../../../contexts/core_context';

import { ChatHistorySearchList } from '../chat_history_search_list';

const setup = () => {
const useChatContextMock = {
sessionId: '1',
setTitle: jest.fn(),
};
const useCoreMock = {
services: coreMock.createStart(),
};
useCoreMock.services.http.put.mockImplementation(() => Promise.resolve());
jest.spyOn(coreContextExports, 'useCore').mockReturnValue(useCoreMock);
jest.spyOn(chatContextExports, 'useChatContext').mockReturnValue(useChatContextMock);

const renderResult = render(
<I18nProvider>
<ChatHistorySearchList
loading={false}
histories={[{ id: '1', title: 'foo', updatedTimeMs: 0 }]}
onSearchChange={jest.fn()}
onLoadChat={jest.fn()}
onRefresh={jest.fn()}
onHistoryDeleted={jest.fn()}
/>
</I18nProvider>
);

return {
useChatContextMock,
renderResult,
};
};

describe('<ChatHistorySearchList />', () => {
it('should set new window title after edit conversation name', async () => {
const { renderResult, useChatContextMock } = setup();

act(() => {
fireEvent.click(renderResult.getByLabelText('Edit conversation name'));
});

act(() => {
fireEvent.change(renderResult.getByLabelText('Conversation name input'), {
target: { value: 'bar' },
});
});

expect(useChatContextMock.setTitle).not.toHaveBeenCalled();

act(() => {
fireEvent.click(renderResult.getByTestId('confirmModalConfirmButton'));
});

waitFor(() => {
expect(useChatContextMock.setTitle).toHaveBeenLastCalledWith('bar');
});
});
});
9 changes: 7 additions & 2 deletions public/tabs/history/chat_history_search_list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import React, { useCallback, useState } from 'react';
import { ChatHistoryList, ChatHistoryListProps } from './chat_history_list';
import { EditConversationNameModal } from '../../components/edit_conversation_name_modal';
import { DeleteConversationConfirmModal } from './delete_conversation_confirm_modal';
import { useChatContext } from '../../contexts';

interface ChatHistorySearchListProps
extends Pick<
Expand Down Expand Up @@ -45,20 +46,24 @@ export const ChatHistorySearchList = ({
onHistoryDeleted,
onChangeItemsPerPage,
}: ChatHistorySearchListProps) => {
const { sessionId, setTitle } = useChatContext();
const [editingConversation, setEditingConversation] = useState<{
id: string;
title: string;
} | null>(null);
const [deletingConversation, setDeletingConversation] = useState<{ id: string } | null>(null);

const handleEditConversationModalClose = useCallback(
(status: 'updated' | string) => {
(status: 'updated' | string, newTitle?: string) => {
if (status === 'updated') {
onRefresh();
if (sessionId === editingConversation?.id) {
setTitle(newTitle);
}
}
setEditingConversation(null);
},
[setEditingConversation, onRefresh]
[setEditingConversation, onRefresh, editingConversation, sessionId, setTitle]
);

const handleDeleteConversationConfirmModalClose = useCallback(
Expand Down
Loading