Skip to content

Commit

Permalink
fix: update conversation data after edit (#48)
Browse files Browse the repository at this point in the history
* fix: update conversation data after edit

Signed-off-by: Lin Wang <wonglam@amazon.com>

* test: add unit tests for update conversation name

Signed-off-by: Lin Wang <wonglam@amazon.com>

---------

Signed-off-by: Lin Wang <wonglam@amazon.com>
  • Loading branch information
wanglam authored Dec 11, 2023
1 parent 9663fb1 commit fa7d6ff
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 4 deletions.
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 @@ -65,7 +65,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

0 comments on commit fa7d6ff

Please sign in to comment.