generated from openedx/frontend-template-application
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Fetches chat history when loading Xpert (#64)
* feat: Fethes chat history when loading Xpert * chore: Added coverage for new API method * chore: Added test:watch script * fix: Fixed tests and made chat history error silent * chore: Added coverage to useMessageHistory() hook * chore: Added test for Xpert to use the new hook * chore: Updated message history hook test * chore: Updated thunk tests for getLearningAssistantMessageHistory() * chore: Updated caniuse-lite db
- Loading branch information
Showing
11 changed files
with
312 additions
and
10 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* eslint-disable no-import-assign */ | ||
import * as auth from '@edx/frontend-platform/auth'; | ||
|
||
import { fetchLearningAssistantMessageHistory } from './api'; | ||
|
||
jest.mock('@edx/frontend-platform/auth'); | ||
|
||
const CHAT_RESPONSE_URL = 'https://some.url/endpoint'; | ||
jest.mock('@edx/frontend-platform', () => ({ | ||
getConfig: () => ({ CHAT_RESPONSE_URL }), | ||
CHAT_RESPONSE_URL, | ||
})); | ||
|
||
describe('API', () => { | ||
afterEach(() => { | ||
jest.restoreAllMocks(); | ||
}); | ||
|
||
describe('fetchLearningAssistantMessageHistory()', () => { | ||
const fakeCourseId = 'course-v1:edx+test+23'; | ||
const apiPayload = [ | ||
{ | ||
role: 'user', | ||
content: 'Marco', | ||
timestamp: '2024-11-04T19:05:07.403363Z', | ||
}, | ||
{ | ||
role: 'assistant', | ||
content: 'Polo', | ||
timestamp: '2024-11-04T19:05:21.357636Z', | ||
}, | ||
]; | ||
|
||
const fakeGet = jest.fn(async () => ({ | ||
data: apiPayload, | ||
catch: () => {}, | ||
})); | ||
|
||
beforeEach(() => { | ||
auth.getAuthenticatedHttpClient = jest.fn(() => ({ | ||
get: fakeGet, | ||
})); | ||
}); | ||
|
||
it('should call the endpoint and process the results', async () => { | ||
const response = await fetchLearningAssistantMessageHistory(fakeCourseId); | ||
|
||
expect(response).toEqual(apiPayload); | ||
expect(fakeGet).toHaveBeenCalledTimes(1); | ||
expect(fakeGet).toHaveBeenCalledWith(`${CHAT_RESPONSE_URL}/${fakeCourseId}/history`); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import { fetchLearningAssistantMessageHistory } from './api'; | ||
|
||
import { getLearningAssistantMessageHistory } from './thunks'; | ||
|
||
jest.mock('./api'); | ||
|
||
describe('Thunks unit tests', () => { | ||
const dispatch = jest.fn(); | ||
|
||
afterEach(() => jest.resetAllMocks()); | ||
|
||
describe('getLearningAssistantMessageHistory()', () => { | ||
const fakeCourseId = 'course-v1:edx+test+23'; | ||
|
||
describe('when returning results', () => { | ||
const apiResponse = [ | ||
{ | ||
role: 'user', | ||
content: 'Marco', | ||
timestamp: '2024-11-04T19:05:07.403363Z', | ||
}, | ||
{ | ||
role: 'assistant', | ||
content: 'Polo', | ||
timestamp: '2024-11-04T19:05:21.357636Z', | ||
}, | ||
]; | ||
|
||
beforeEach(() => { | ||
fetchLearningAssistantMessageHistory.mockResolvedValue(apiResponse); | ||
}); | ||
|
||
it('should set the loading state, fetch, parse and set the messages and remove the loading state', async () => { | ||
await getLearningAssistantMessageHistory(fakeCourseId)(dispatch); | ||
|
||
expect(dispatch).toHaveBeenNthCalledWith(1, { | ||
type: 'learning-assistant/setApiIsLoading', | ||
payload: true, | ||
}); | ||
|
||
expect(fetchLearningAssistantMessageHistory).toHaveBeenCalledWith(fakeCourseId); | ||
|
||
expect(dispatch).toHaveBeenNthCalledWith(2, { | ||
type: 'learning-assistant/setMessageList', | ||
payload: { | ||
messageList: apiResponse.map(({ timestamp, ...msg }) => ({ | ||
...msg, | ||
timestamp: new Date(timestamp), // Parse ISO time to Date() | ||
})), | ||
}, | ||
}); | ||
|
||
expect(dispatch).toHaveBeenNthCalledWith(3, { | ||
type: 'learning-assistant/setDisclosureAcknowledged', | ||
payload: true, | ||
}); | ||
|
||
expect(dispatch).toHaveBeenNthCalledWith(4, { | ||
type: 'learning-assistant/setApiIsLoading', | ||
payload: false, | ||
}); | ||
}); | ||
}); | ||
|
||
describe('when returning no messages', () => { | ||
const apiResponse = []; | ||
|
||
beforeEach(() => { | ||
fetchLearningAssistantMessageHistory.mockResolvedValue(apiResponse); | ||
}); | ||
|
||
it('should only set and remove the loading state', async () => { | ||
await getLearningAssistantMessageHistory(fakeCourseId)(dispatch); | ||
|
||
expect(dispatch).toHaveBeenNthCalledWith(1, { | ||
type: 'learning-assistant/setApiIsLoading', | ||
payload: true, | ||
}); | ||
|
||
expect(fetchLearningAssistantMessageHistory).toHaveBeenCalledWith(fakeCourseId); | ||
|
||
expect(dispatch).not.toHaveBeenCalledWith( | ||
expect.objectContaining({ type: 'learning-assistant/setMessageList' }), | ||
); | ||
|
||
expect(dispatch).not.toHaveBeenCalledWith( | ||
expect.objectContaining({ type: 'learning-assistant/setDisclosureAcknowledged' }), | ||
); | ||
|
||
expect(dispatch).toHaveBeenNthCalledWith(2, { | ||
type: 'learning-assistant/setApiIsLoading', | ||
payload: false, | ||
}); | ||
}); | ||
}); | ||
|
||
describe('when throwing on fetching', () => { | ||
beforeEach(() => { | ||
fetchLearningAssistantMessageHistory.mockRejectedValue('Whoopsie!'); | ||
}); | ||
|
||
it('should only set and remove the loading state', async () => { | ||
await getLearningAssistantMessageHistory(fakeCourseId)(dispatch); | ||
|
||
expect(dispatch).toHaveBeenNthCalledWith(1, { | ||
type: 'learning-assistant/setApiIsLoading', | ||
payload: true, | ||
}); | ||
|
||
expect(fetchLearningAssistantMessageHistory).toHaveBeenCalledWith(fakeCourseId); | ||
|
||
expect(dispatch).not.toHaveBeenCalledWith( | ||
expect.objectContaining({ type: 'learning-assistant/setMessageList' }), | ||
); | ||
|
||
expect(dispatch).not.toHaveBeenCalledWith( | ||
expect.objectContaining({ type: 'learning-assistant/setDisclosureAcknowledged' }), | ||
); | ||
|
||
expect(dispatch).toHaveBeenNthCalledWith(2, { | ||
type: 'learning-assistant/setApiIsLoading', | ||
payload: false, | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/* eslint-disable import/prefer-default-export */ | ||
export { useMessageHistory } from './message-history'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/* eslint-disable import/prefer-default-export */ | ||
import { useEffect } from 'react'; | ||
import { useDispatch, useSelector } from 'react-redux'; | ||
import { getLearningAssistantMessageHistory } from '../data/thunks'; | ||
|
||
export const useMessageHistory = (courseId) => { | ||
const dispatch = useDispatch(); | ||
const { isEnabled } = useSelector(state => state.learningAssistant); | ||
|
||
useEffect(() => { | ||
if (!courseId || !isEnabled) { return; } | ||
|
||
dispatch(getLearningAssistantMessageHistory(courseId)); | ||
}, [dispatch, isEnabled, courseId]); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { renderHook } from '@testing-library/react-hooks'; | ||
|
||
import { useSelector } from 'react-redux'; | ||
import { useMessageHistory } from './message-history'; | ||
import { getLearningAssistantMessageHistory } from '../data/thunks'; | ||
|
||
const mockDispatch = jest.fn(); | ||
jest.mock('react-redux', () => ({ | ||
...jest.requireActual('react-redux'), | ||
useSelector: jest.fn(), | ||
useDispatch: () => mockDispatch, | ||
})); | ||
|
||
const getLearningAssistantMessageHistorySignature = { getLearningAssistantMessageHistory: 'getLearningAssistantMessageHistory' }; | ||
jest.mock('../data/thunks', () => ({ | ||
getLearningAssistantMessageHistory: jest.fn().mockReturnValue(getLearningAssistantMessageHistorySignature), | ||
})); | ||
|
||
describe('Learning Assistant Message History Hooks', () => { | ||
afterEach(() => { | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
describe('useMessageHistory()', () => { | ||
let hook; | ||
const fakeCourseId = 'course-v1:edx+test+23'; | ||
|
||
const renderTestHook = (courseId, isEnabled) => { | ||
const mockedStoreState = { learningAssistant: { isEnabled } }; | ||
useSelector.mockImplementation(selector => selector(mockedStoreState)); | ||
hook = renderHook(() => useMessageHistory(courseId)); | ||
return hook; | ||
}; | ||
|
||
it('should dispatch getLearningAssistantMessageHistory() with the chat history', () => { | ||
renderTestHook(fakeCourseId, true); | ||
|
||
expect(mockDispatch).toHaveBeenCalledTimes(1); | ||
expect(mockDispatch).toHaveBeenCalledWith(getLearningAssistantMessageHistorySignature); | ||
expect(getLearningAssistantMessageHistory).toHaveBeenCalledWith(fakeCourseId); | ||
}); | ||
|
||
it('should NOT dispatch getLearningAssistantMessageHistory() when disabled', () => { | ||
renderTestHook(fakeCourseId, false); | ||
|
||
expect(mockDispatch).not.toHaveBeenCalled(); | ||
expect(getLearningAssistantMessageHistory).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('should NOT dispatch getLearningAssistantMessageHistory() with no courseId', () => { | ||
renderTestHook(null, true); | ||
|
||
expect(mockDispatch).not.toHaveBeenCalled(); | ||
expect(getLearningAssistantMessageHistory).not.toHaveBeenCalled(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.