Compare commits

...

3 Commits

Author SHA1 Message Date
sundasnoreen12
1005541e8d refactor: added url changes 2023-03-13 11:51:43 +05:00
sundasnoreen12
562db39c24 refactor: fixed changes for code optimization 2023-03-13 11:51:33 +05:00
sundasnoreen12
d6a38ae6d7 test: added test cases for learner view 2023-03-13 11:51:33 +05:00
5 changed files with 206 additions and 49 deletions

View File

@@ -72,6 +72,7 @@ function Search({ intl }) {
<Icon <Icon
src={SearchIcon} src={SearchIcon}
onClick={() => onSubmit(searchValue)} onClick={() => onSubmit(searchValue)}
data-testid="search-icon"
/> />
</span> </span>
</SearchField.Advanced> </SearchField.Advanced>

View File

@@ -8,14 +8,13 @@ ensureConfig([
], 'Posts API service'); ], 'Posts API service');
export const getCourseConfigApiUrl = () => `${getConfig().LMS_BASE_URL}/api/discussion/v1/courses/`; export const getCourseConfigApiUrl = () => `${getConfig().LMS_BASE_URL}/api/discussion/v1/courses/`;
export const getDiscussionsConfigUrl = (courseId) => `${getCourseConfigApiUrl()}${courseId}/`;
/** /**
* Get discussions course config * Get discussions course config
* @param {string} courseId * @param {string} courseId
*/ */
export async function getDiscussionsConfig(courseId) { export async function getDiscussionsConfig(courseId) {
const url = `${getCourseConfigApiUrl()}${courseId}/`; const { data } = await getAuthenticatedHttpClient().get(getDiscussionsConfigUrl(courseId));
const { data } = await getAuthenticatedHttpClient().get(url);
return data; return data;
} }
@@ -24,7 +23,7 @@ export async function getDiscussionsConfig(courseId) {
* @param {string} courseId * @param {string} courseId
*/ */
export async function getDiscussionsSettings(courseId) { export async function getDiscussionsSettings(courseId) {
const url = `${getCourseConfigApiUrl()}${courseId}/settings`; const url = `${getDiscussionsConfigUrl(courseId)}settings`;
const { data } = await getAuthenticatedHttpClient().get(url); const { data } = await getAuthenticatedHttpClient().get(url);
return data; return data;
} }

View File

@@ -1,6 +1,8 @@
import React from 'react'; import React from 'react';
import { render, screen } from '@testing-library/react'; import {
fireEvent, render, screen, waitFor, within,
} from '@testing-library/react';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import { IntlProvider } from 'react-intl'; import { IntlProvider } from 'react-intl';
@@ -11,11 +13,13 @@ import { initializeMockApp } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { AppProvider } from '@edx/frontend-platform/react'; import { AppProvider } from '@edx/frontend-platform/react';
import { PostActionsBar } from '../../components';
import { initializeStore } from '../../store'; import { initializeStore } from '../../store';
import { executeThunk } from '../../test-utils'; import { executeThunk } from '../../test-utils';
import { getCourseConfigApiUrl } from '../data/api'; import { DiscussionContext } from '../common/context';
import { getDiscussionsConfigUrl } from '../data/api';
import { fetchCourseConfig } from '../data/thunks'; import { fetchCourseConfig } from '../data/thunks';
import { getCoursesApiUrl, getUserProfileApiUrl } from './data/api'; import { getUserProfileApiUrl, learnersApiUrl } from './data/api';
import { fetchLearners } from './data/thunks'; import { fetchLearners } from './data/thunks';
import LearnersView from './LearnersView'; import LearnersView from './LearnersView';
@@ -23,27 +27,31 @@ import './data/__factories__';
let store; let store;
let axiosMock; let axiosMock;
const coursesApiUrl = getCoursesApiUrl();
const courseConfigApiUrl = getCourseConfigApiUrl();
const userProfileApiUrl = getUserProfileApiUrl();
const courseId = 'course-v1:edX+TestX+Test_Course'; const courseId = 'course-v1:edX+TestX+Test_Course';
let container;
function renderComponent() { function renderComponent() {
return render( const wrapper = render(
<IntlProvider locale="en"> <IntlProvider locale="en">
<AppProvider store={store}> <AppProvider store={store}>
<DiscussionContext.Provider value={{
page: 'learners',
}}
>
<MemoryRouter initialEntries={[`/${courseId}/`]}> <MemoryRouter initialEntries={[`/${courseId}/`]}>
<Route path="/:courseId/"> <Route path="/:courseId/">
<PostActionsBar />
<LearnersView /> <LearnersView />
</Route> </Route>
</MemoryRouter> </MemoryRouter>
</DiscussionContext.Provider>
</AppProvider> </AppProvider>
</IntlProvider>, </IntlProvider>,
); );
container = wrapper.container;
} }
describe('LearnersView', () => { describe('LearnersView', () => {
const learnerCount = 3;
beforeEach(async () => { beforeEach(async () => {
initializeMockApp({ initializeMockApp({
authenticatedUser: { authenticatedUser: {
@@ -53,41 +61,190 @@ describe('LearnersView', () => {
roles: [], roles: [],
}, },
}); });
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
store = initializeStore(); store = initializeStore();
Factory.resetAll(); Factory.resetAll();
const learnersData = Factory.build('learnersResult', {}, {
count: learnerCount,
pageSize: 6,
});
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock.onGet(`${coursesApiUrl}${courseId}/activity_stats/`)
.reply(() => [200, learnersData]);
const learnersProfile = Factory.build('learnersProfile', {}, {
username: ['learner-1', 'learner-2', 'learner-3'],
});
axiosMock.onGet(`${userProfileApiUrl}?username=learner-1,learner-2,learner-3`)
.reply(() => [200, learnersProfile.profiles]);
await executeThunk(fetchLearners(courseId), store.dispatch, store.getState);
}); });
describe('Basic', () => { async function setUpLearnerMockResponse(
test('Learners tab is disabled by default', async () => { count = 3,
await act(async () => { pageSize = 6,
await renderComponent(); page = 1,
username = ['learner-1', 'learner-2', 'learner-3'],
searchText,
) {
Factory.resetAll();
const learnersData = Factory.build('learnersResult', {}, {
count,
pageSize,
page,
}); });
expect(screen.queryByText(/Last active/i)).toBeFalsy(); axiosMock.onGet(learnersApiUrl(courseId))
}); .reply(() => [200, learnersData]);
test('Learners tab is enabled', async () => {
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`).reply(200, { axiosMock.onGet(`${getUserProfileApiUrl()}?username=${username.join()}`)
.reply(() => [200, Factory.build('learnersProfile', {}, {
username,
}).profiles]);
await executeThunk(fetchLearners(courseId, { usernameSearch: searchText }), store.dispatch, store.getState);
}
async function assignPrivilages() {
axiosMock.onGet(getDiscussionsConfigUrl(courseId)).reply(200, {
learners_tab_enabled: true, learners_tab_enabled: true,
user_is_privileged: true, user_is_privileged: true,
}); });
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/settings`).reply(200, {});
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState); await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
await act(async () => { }
test('Learners tab is disabled by default', async () => {
await setUpLearnerMockResponse();
await renderComponent(); await renderComponent();
expect(screen.queryByText('learner-1')).not.toBeInTheDocument();
});
test('Learners tab is enabled', async () => {
await setUpLearnerMockResponse();
await assignPrivilages();
await waitFor(() => {
renderComponent();
});
expect(screen.queryByText('learner-1')).toBeInTheDocument();
});
test('Most activity should be selected by default for the non-moderator role.', async () => {
await setUpLearnerMockResponse();
await renderComponent();
const filterBar = container.querySelector('.collapsible-trigger');
await act(async () => {
fireEvent.click(filterBar);
});
await waitFor(() => {
const mostActivity = screen.getByTestId('activity selected');
expect(mostActivity).toBeInTheDocument();
}); });
}); });
it.each([
{ searchBy: 'sort-recency', result: 0 },
{ searchBy: 'sort-activity', result: 3 },
])('successfully display learners by %s.', async ({ searchBy, result }) => {
await setUpLearnerMockResponse();
await assignPrivilages();
await renderComponent();
const filterBar = container.querySelector('.collapsible-trigger');
await act(async () => {
fireEvent.click(filterBar);
});
await waitFor(async () => {
const activity = container.querySelector(`#${searchBy}`);
await act(async () => {
fireEvent.click(activity);
});
await waitFor(() => {
const learners = container.querySelectorAll('.discussion-post');
expect(learners).toHaveLength(result);
});
});
});
it('should display a learner\'s list.', async () => {
await setUpLearnerMockResponse();
await assignPrivilages();
await waitFor(() => {
renderComponent();
});
const learners = await container.querySelectorAll('.discussion-post');
const firstLearner = learners.item(0);
const learnerAvatar = firstLearner.querySelector('[alt=learner-1]');
const learnerTitle = within(firstLearner).queryByText('learner-1');
const stats = firstLearner.querySelectorAll('.icon-size');
expect(learners).toHaveLength(3);
expect(learnerAvatar).toBeInTheDocument();
expect(learnerTitle).toBeInTheDocument();
expect(stats).toHaveLength(2);
});
it.each([
{
searchText: 'hello world',
output: 'Showing 0 results for',
learnersCount: 0,
username: [],
},
{
searchText: 'learner',
output: 'Showing 2 results for',
learnersCount: 2,
username:
['learner-1', 'learner-2'],
},
])('should have a search bar with a clear button and \'$output\' results found text.',
async ({
searchText, output, learnersCount, username,
}) => {
await setUpLearnerMockResponse();
await assignPrivilages();
await renderComponent();
const searchField = within(container).getByPlaceholderText('Search learners');
const searchButton = within(container).getByTestId('search-icon');
await fireEvent.change(searchField, { target: { value: searchText } });
await act(async () => {
fireEvent.click(searchButton);
setUpLearnerMockResponse(learnersCount, learnersCount, 1, username, searchText);
});
await waitFor(() => {
const clearButton = within(container).queryByText('Clear results');
const searchMessage = within(container).queryByText(`${output} "${searchText}"`);
const leaners = container.querySelectorAll('.discussion-post') ?? [];
expect(searchMessage).toBeInTheDocument();
expect(clearButton).toBeInTheDocument();
expect(leaners).toHaveLength(learnersCount);
});
});
test('When click on the clear button it should move to a list of all learners.', async () => {
await setUpLearnerMockResponse();
await assignPrivilages();
await renderComponent();
const searchField = within(container).getByPlaceholderText('Search learners');
const searchButton = within(container).getByTestId('search-icon');
let clearButton;
await fireEvent.change(searchField, { target: { value: 'learner' } });
await act(async () => {
fireEvent.click(searchButton);
setUpLearnerMockResponse(2, 2, 1, ['learner-1', 'learner-2'], 'learner');
});
await waitFor(() => {
clearButton = within(container).queryByText('Clear results');
});
await act(async () => fireEvent.click(clearButton));
await waitFor(() => {
setUpLearnerMockResponse();
});
const learners = container.querySelectorAll('.discussion-post');
expect(learners).toHaveLength(3);
}); });
}); });

View File

@@ -13,9 +13,9 @@ Factory.define('learner')
}); });
Factory.define('learnersResult') Factory.define('learnersResult')
.option('count', null, 3) .option('count', null)
.option('page', null, 1) .option('page', null)
.option('pageSize', null, 5) .option('pageSize', null)
.option('courseId', null, 'course-v1:Test+TestX+Test_Course') .option('courseId', null, 'course-v1:Test+TestX+Test_Course')
.option('activeFlags', null, 0) .option('activeFlags', null, 0)
.attr( .attr(

View File

@@ -24,7 +24,7 @@ const ActionItem = ({
<label <label
htmlFor={id} htmlFor={id}
className="focus border-bottom-0 d-flex align-items-center w-100 py-2 m-0 font-weight-500 filter-menu" className="focus border-bottom-0 d-flex align-items-center w-100 py-2 m-0 font-weight-500 filter-menu"
data-testid={value === selected ? 'selected' : null} data-testid={value === selected ? `${value} selected` : null}
style={{ cursor: 'pointer' }} style={{ cursor: 'pointer' }}
aria-checked={value === selected} aria-checked={value === selected}
> >