Skip to content

Commit

Permalink
fix: added unit tests for AI Curation (#418)
Browse files Browse the repository at this point in the history
Co-authored-by: Maham Akif <[email protected]>
  • Loading branch information
mahamakifdar19 and Maham Akif authored Jun 13, 2024
1 parent 093f2dc commit d517815
Show file tree
Hide file tree
Showing 8 changed files with 437 additions and 1 deletion.
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ const { createConfig } = require('@openedx/frontend-build');

module.exports = createConfig('jest', {
setupFiles: ['<rootDir>/src/setupTest.js'],
coveragePathIgnorePatterns: ['src/setupTest.js', 'src/i18n', 'src/components/aiCuration', 'src/index.jsx'],
coveragePathIgnorePatterns: ['src/setupTest.js', 'src/i18n', 'src/index.jsx'],
});
1 change: 1 addition & 0 deletions src/components/aiCuration/AskXpert.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ const AskXpert = ({
src={Close}
iconAs={Icon}
onClick={() => onClose()}
aria-label="Close Ask Xpert Modal"
/>
<p className="text-white">
<FormattedMessage
Expand Down
1 change: 1 addition & 0 deletions src/components/aiCuration/AskXpertQueryField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const AskXpertQueryField = ({ onSubmit, isDisabled }) => {
onSubmit(textInputValue);
}
}}
aria-label="Enter Ask Xpert Query"
/>
)}
/>
Expand Down
130 changes: 130 additions & 0 deletions src/components/aiCuration/tests/AskXpert.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import React from 'react';
import {
render, screen, fireEvent,
} from '@testing-library/react';
import axios from 'axios';
import '@testing-library/jest-dom/extend-expect';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import AskXpert from '../AskXpert';
import EnterpriseCatalogAiCurationApiService from '../data/service';

jest.mock('axios');

const messages = {
en: {
'catalogPage.askXpert.title': 'Xpert',
'catalogPage.askXpert.description': 'Use AI to narrow your search.',
'catalogPage.askXpert.loadingMessage': 'Xpert is thinking! Wait with us while we generate a catalog for <b> “{query}”...</b>',
'catalogPage.askXpert.cancel': 'Cancel',
},
};

describe('AskXpert Component', () => {
const mockCatalogName = 'Mock Catalog';
const mockOnClose = jest.fn();
const mockOnXpertData = jest.fn();
const mockTaskId = 'mock-task-id';
const mockThreshold = 0.5;
const mockResponseData = { status: 200, data: { result: {} } };
const mockErrorResponse = { status: 404, data: { error: 'Not Found' } };

afterEach(() => {
jest.clearAllMocks();
});

test('renders AskXpert component correctly', () => {
render(
<IntlProvider locale="en" messages={messages.en}>
<AskXpert
catalogName={mockCatalogName}
onClose={mockOnClose}
onXpertData={mockOnXpertData}
/>
</IntlProvider>,
);

expect(screen.getByText(/Xpert/i)).toBeInTheDocument();
expect(screen.getByText(/Use AI to narrow your search/i)).toBeInTheDocument();
});

test('calls onClose when close button is clicked', () => {
render(
<IntlProvider locale="en" messages={messages.en}>
<AskXpert
catalogName={mockCatalogName}
onClose={mockOnClose}
onXpertData={mockOnXpertData}
/>
</IntlProvider>,
);

fireEvent.click(screen.getByLabelText('Close Ask Xpert Modal'));
expect(mockOnClose).toHaveBeenCalled();
});

test('postXpertQuery makes a POST request to the correct endpoint', async () => {
const mockQuery = 'mock query';

axios.post.mockResolvedValueOnce(mockResponseData);

const response = await EnterpriseCatalogAiCurationApiService.postXpertQuery(mockQuery, mockCatalogName);

expect(axios.post).toHaveBeenCalledTimes(1);
expect(axios.post).toHaveBeenCalledWith(
`${EnterpriseCatalogAiCurationApiService.enterpriseCatalogAiCurationServiceUrl}`,
{ query: mockQuery, catalog_name: mockCatalogName },
);
expect(response).toEqual(mockResponseData);
});

test('postXpertQuery handles errors and returns appropriate response', async () => {
const mockQuery = 'mock query';

axios.post.mockRejectedValueOnce({ response: mockErrorResponse });

const response = await EnterpriseCatalogAiCurationApiService.postXpertQuery(mockQuery, mockCatalogName);

expect(axios.post).toHaveBeenCalledTimes(1);
expect(axios.post).toHaveBeenCalledWith(
`${EnterpriseCatalogAiCurationApiService.enterpriseCatalogAiCurationServiceUrl}`,
{ query: mockQuery, catalog_name: mockCatalogName },
);
expect(response).toEqual(mockErrorResponse);
});

test('getXpertResults makes a GET request to the correct endpoint with taskId', async () => {
axios.get.mockResolvedValueOnce(mockResponseData);

const response = await EnterpriseCatalogAiCurationApiService.getXpertResults(mockTaskId);

expect(axios.get).toHaveBeenCalledTimes(1);
expect(axios.get).toHaveBeenCalledWith(
`${EnterpriseCatalogAiCurationApiService.enterpriseCatalogAiCurationServiceUrl}?task_id=${mockTaskId}`,
);
expect(response).toEqual(mockResponseData);
});

test('getXpertResults makes a GET request with taskId and threshold', async () => {
axios.get.mockResolvedValueOnce(mockResponseData);

const response = await EnterpriseCatalogAiCurationApiService.getXpertResults(mockTaskId, mockThreshold);

expect(axios.get).toHaveBeenCalledTimes(1);
expect(axios.get).toHaveBeenCalledWith(
`${EnterpriseCatalogAiCurationApiService.enterpriseCatalogAiCurationServiceUrl}?task_id=${mockTaskId}&threshold=${mockThreshold}`,
);
expect(response).toEqual(mockResponseData);
});

test('getXpertResults handles errors and returns appropriate response', async () => {
axios.get.mockRejectedValueOnce({ response: mockErrorResponse });

const response = await EnterpriseCatalogAiCurationApiService.getXpertResults(mockTaskId);

expect(axios.get).toHaveBeenCalledTimes(1);
expect(axios.get).toHaveBeenCalledWith(
`${EnterpriseCatalogAiCurationApiService.enterpriseCatalogAiCurationServiceUrl}?task_id=${mockTaskId}`,
);
expect(response).toEqual(mockErrorResponse);
});
});
57 changes: 57 additions & 0 deletions src/components/aiCuration/tests/AskXpertQueryField.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import {
render, screen, fireEvent, waitFor,
} from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import AskXpertQueryField from '../AskXpertQueryField';

describe('AskXpertQueryField Component', () => {
const mockOnSubmit = jest.fn();
const mockIsDisabled = false;
const placeholderText = 'Describe a skill, competency or job title you are trying to train';

afterEach(() => {
jest.clearAllMocks();
});

test('renders AskXpertQueryField component correctly', () => {
render(<AskXpertQueryField onSubmit={mockOnSubmit} isDisabled={mockIsDisabled} />);

const inputElement = screen.getByPlaceholderText(placeholderText);
expect(inputElement).toBeInTheDocument();
expect(inputElement).toBeEnabled();
});

test('triggers onSubmit callback when Send button is clicked', async () => {
render(<AskXpertQueryField onSubmit={mockOnSubmit} isDisabled={mockIsDisabled} />);

const inputElement = screen.getByPlaceholderText(placeholderText);
fireEvent.change(inputElement, { target: { value: 'Mock query' } });
fireEvent.keyDown(inputElement, { key: 'Enter', code: 'Enter' });

await waitFor(() => {
expect(mockOnSubmit).toHaveBeenCalledTimes(1);
});
});

test('does not trigger onSubmit callback when input is empty', () => {
render(<AskXpertQueryField onSubmit={mockOnSubmit} isDisabled={mockIsDisabled} />);

const inputElement = screen.getByPlaceholderText(placeholderText);

fireEvent.change(inputElement, { target: { value: '' } });
fireEvent.keyDown(inputElement, { key: 'Enter', code: 'Enter' });

expect(mockOnSubmit).not.toHaveBeenCalled();
});

test('does not trigger onSubmit callback when component is disabled', () => {
render(<AskXpertQueryField onSubmit={mockOnSubmit} isDisabled />);

const inputElement = screen.getByPlaceholderText(placeholderText);
fireEvent.keyDown(inputElement, { key: 'Enter', code: 'Enter' });
fireEvent.click(screen.getByLabelText('Enter Ask Xpert Query'));

expect(mockOnSubmit).not.toHaveBeenCalled();
});
});
124 changes: 124 additions & 0 deletions src/components/aiCuration/tests/XpertResultCard.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import React from 'react';
import {
render, screen, fireEvent, waitFor,
} from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import XpertResultCard from '../xpertResultCard/XpertResultCard';
import { useXpertResultsWithThreshold } from '../data/hooks';
import { CONTENT_TYPE_COURSE, CONTENT_TYPE_PROGRAM, EXEC_ED_TITLE } from '../../../constants';

jest.mock('../data/hooks', () => ({
useXpertResultsWithThreshold: jest.fn(),
}));

const mockGetXpertResultsWithThreshold = jest.fn();

const messages = {
'catalogs.askXpert.result.card.heading': 'Xpert',
'catalogs.askXpert.result.card.label.for.results': 'Results: {query}',
'catalogs.askXpert.result.card.label.for.self.paced.courses': 'Self-paced courses',
'catalogs.askXpert.result.card.label.for.self.paced.programs': 'Self-paced programs',
'catalogs.askXpert.result.card.label.for.executive.education.courses': 'Executive education courses',
'catalogs.askXpert.result.card.error.label': 'An error occurred. Please try a new search',
};

const defaultProps = {
taskId: 'task-id',
query: 'test query',
results: {
ocm_courses: [],
exec_ed_courses: [],
programs: [],
},
onClose: jest.fn(),
onXpertResults: jest.fn(),
};

const renderXpertResultCard = (props = {}) => render(
<IntlProvider locale="en" messages={messages}>
<XpertResultCard {...defaultProps} {...props} />
</IntlProvider>,
);

describe('XpertResultCard', () => {
beforeEach(() => {
useXpertResultsWithThreshold.mockReturnValue({
loading: false,
error: null,
xpertResultsData: {},
getXpertResultsWithThreshold: mockGetXpertResultsWithThreshold,
});
});

afterEach(() => {
jest.clearAllMocks();
});

it('renders correctly with default props', () => {
renderXpertResultCard();
expect(screen.getByText('Xpert')).toBeInTheDocument();
expect(screen.getByText('Results:')).toBeInTheDocument();
expect(screen.getByText('Self-paced courses')).toBeInTheDocument();
expect(screen.getByText('Self-paced programs')).toBeInTheDocument();
expect(screen.getByText('Executive education courses')).toBeInTheDocument();
});

it('calls onClose when New Search button is clicked', () => {
renderXpertResultCard();
fireEvent.click(screen.getByText('New Search'));
expect(defaultProps.onClose).toHaveBeenCalledTimes(1);
});

it('calls onClose when Close icon is clicked', () => {
renderXpertResultCard();
fireEvent.click(screen.getByLabelText('Close XpertResultCard'));
expect(defaultProps.onClose).toHaveBeenCalledTimes(1);
});

it('updates the threshold value and triggers API call on slider change', async () => {
renderXpertResultCard();
const slider = screen.getByLabelText('Xpert result card slider');
fireEvent.change(slider, { target: { value: '0.4' } });

await waitFor(() => expect(mockGetXpertResultsWithThreshold).toHaveBeenCalledTimes(1), { timeout: 1500 });
expect(mockGetXpertResultsWithThreshold).toHaveBeenCalledWith('task-id', 0.4);
});

it('displays error message when there is an error', () => {
useXpertResultsWithThreshold.mockReturnValue({
loading: false,
error: 'An error occurred',
xpertResultsData: {},
getXpertResultsWithThreshold: mockGetXpertResultsWithThreshold,
});

renderXpertResultCard();
expect(screen.getByText('An error occurred. Please try a new search')).toBeInTheDocument();
});

it('updates xpertResults and calls onXpertResults when xpertResultsData changes', async () => {
const newXpertResultsData = {
ocm_courses: [{ aggregation_key: 'key1' }],
exec_ed_courses: [{ aggregation_key: 'key2' }],
programs: [{ aggregation_key: 'key3' }],
};

useXpertResultsWithThreshold.mockReturnValue({
loading: false,
error: null,
xpertResultsData: newXpertResultsData,
getXpertResultsWithThreshold: mockGetXpertResultsWithThreshold,
});

renderXpertResultCard();

await waitFor(() => {
expect(defaultProps.onXpertResults).toHaveBeenCalledWith({
[CONTENT_TYPE_COURSE]: ['key1'],
[EXEC_ED_TITLE]: ['key2'],
[CONTENT_TYPE_PROGRAM]: ['key3'],
});
});
});
});
Loading

0 comments on commit d517815

Please sign in to comment.