From a3219225dbf6fc5a141c2ae8c04793b148e2483c Mon Sep 17 00:00:00 2001 From: Mehak Nasir <67791278+mehaknasir@users.noreply.github.com> Date: Tue, 11 May 2021 16:10:20 +0500 Subject: [PATCH] test:Applist test cases added (#113) Co-authored-by: MehakNasir --- .../discussions/app-list/AppCard.jsx | 7 +- .../discussions/app-list/AppList.jsx | 9 +- .../discussions/app-list/AppList.test.jsx | 100 ++++++++++++++++-- .../discussions/data/redux.test.js | 8 +- .../discussions/factories/mockApiResponses.js | 15 +++ src/utils.js | 6 ++ 6 files changed, 123 insertions(+), 22 deletions(-) create mode 100644 src/utils.js diff --git a/src/pages-and-resources/discussions/app-list/AppCard.jsx b/src/pages-and-resources/discussions/app-list/AppCard.jsx index c0f554c7f..441fc7587 100644 --- a/src/pages-and-resources/discussions/app-list/AppCard.jsx +++ b/src/pages-and-resources/discussions/app-list/AppCard.jsx @@ -2,8 +2,9 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import Responsive from 'react-responsive'; import { - Card, CheckboxControl, ExtraSmall, + Card, CheckboxControl, breakpoints, } from '@edx/paragon'; import messages from './messages'; import FeaturesList from './FeaturesList'; @@ -51,12 +52,12 @@ function AppCard({ {supportText} {intl.formatMessage(messages[`appDescription-${app.id}`])} - + - + ); diff --git a/src/pages-and-resources/discussions/app-list/AppList.jsx b/src/pages-and-resources/discussions/app-list/AppList.jsx index b303a6515..58dcea7a2 100644 --- a/src/pages-and-resources/discussions/app-list/AppList.jsx +++ b/src/pages-and-resources/discussions/app-list/AppList.jsx @@ -1,11 +1,10 @@ import React, { useCallback, useEffect } from 'react'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { CardGrid, Container, LargerThanExtraSmall } from '@edx/paragon'; +import { CardGrid, Container, breakpoints } from '@edx/paragon'; import { useDispatch, useSelector } from 'react-redux'; - +import Responsive from 'react-responsive'; import { useModels } from '../../../generic/model-store'; import { selectApp, LOADED, LOADING } from '../data/slice'; - import AppCard from './AppCard'; import messages from './messages'; import FeaturesTable from './FeaturesTable'; @@ -73,7 +72,7 @@ function AppList({ intl }) { /> ))} - +

{intl.formatMessage(messages.supportedFeatures)}

@@ -83,7 +82,7 @@ function AppList({ intl }) { features={features} /> -
+ ); } diff --git a/src/pages-and-resources/discussions/app-list/AppList.test.jsx b/src/pages-and-resources/discussions/app-list/AppList.test.jsx index 4a21ffcec..fa2ea9cb2 100644 --- a/src/pages-and-resources/discussions/app-list/AppList.test.jsx +++ b/src/pages-and-resources/discussions/app-list/AppList.test.jsx @@ -1,21 +1,107 @@ +import React from 'react'; + +import { initializeMockApp } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; +import { AppProvider } from '@edx/frontend-platform/react'; +import { breakpoints } from '@edx/paragon'; +import { + queryByText, render, queryAllByRole, queryByRole, getByRole, queryByLabelText, getByLabelText, queryAllByText, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import MockAdapter from 'axios-mock-adapter'; +import { Context as ResponsiveContext } from 'react-responsive'; + +import initializeStore from '../../../store'; +import executeThunk from '../../../utils'; +import { getAppsUrl } from '../data/api'; +import { fetchApps } from '../data/thunks'; +import { emptyAppApiResponse, legacyApiResponse, piazzaApiResponse } from '../factories/mockApiResponses'; +import AppList from './AppList'; +import messages from './messages'; + +const courseId = 'course-v1:edX+TestX+Test_Course'; + describe('AppList', () => { - test('displays a message when there are no apps available', () => { + let axiosMock; + let store; + let container; + function createComponent(screenWidth = breakpoints.extraLarge.minWidth) { + return ( + + + + + + + + ); + } + + beforeEach(async () => { + initializeMockApp({ + authenticatedUser: { + userId: 3, + username: 'abc123', + administrator: true, + roles: [], + }, + }); + + store = await initializeStore(); + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); }); - test('display a card for each available app', () => { + const mockStore = async (mockResponse, screenWidth = breakpoints.extraLarge.minWidth) => { + axiosMock.onGet(getAppsUrl(courseId)).reply(200, mockResponse); + await executeThunk(fetchApps(courseId), store.dispatch); + const component = createComponent(screenWidth); + const wrapper = render(component); + container = wrapper.container; + }; + test('displays a message when there are no apps available', async () => { + await mockStore(emptyAppApiResponse); + expect(queryByText(container, `${messages.noApps.defaultMessage}`)).toBeInTheDocument(); }); - test('displays the FeaturesTable at desktop sizes', () => { - + test('displays loading state when there is no active App', async () => { + await mockStore({}); + expect(queryByRole(container, 'status')).toBeInTheDocument(); }); - test('hides the FeaturesTable at mobile sizes', () => { - + test('display a card for each available app', async () => { + await mockStore(piazzaApiResponse); + const appCount = store.getState().discussions.appIds.length; + expect(queryAllByRole(container, 'radio')).toHaveLength(appCount); }); - test('onSelectApp is called when an app is clicked', () => { + test('displays the FeaturesTable at desktop sizes', async () => { + await mockStore(piazzaApiResponse); + expect(queryByRole(container, 'table')).toBeInTheDocument(); + }); + test('hides the FeaturesTable at mobile sizes', async () => { + await mockStore(piazzaApiResponse, breakpoints.extraSmall.maxWidth); + expect(queryByRole(container, 'table')).not.toBeInTheDocument(); + }); + + test('hides the FeaturesList at desktop sizes', async () => { + await mockStore(piazzaApiResponse); + expect(queryByText(container, messages['supportedFeatureList-mobile-show'].defaultMessage)).not.toBeInTheDocument(); + }); + + test('displays the FeaturesList at mobile sizes', async () => { + await mockStore(piazzaApiResponse, breakpoints.extraSmall.maxWidth); + const appCount = store.getState().discussions.appIds.length; + expect(queryAllByText(container, messages['supportedFeatureList-mobile-show'].defaultMessage)).toHaveLength(appCount); + }); + + test('selectApp is called when an app is clicked', async () => { + await mockStore(legacyApiResponse); + userEvent.click(getByLabelText(container, 'Select Piazza')); + const clickedCard = getByRole(container, 'radio', { checked: true }); + expect(queryByLabelText(clickedCard, 'Select Piazza')).toBeInTheDocument(); }); }); diff --git a/src/pages-and-resources/discussions/data/redux.test.js b/src/pages-and-resources/discussions/data/redux.test.js index 8392b35fb..fd41aa50c 100644 --- a/src/pages-and-resources/discussions/data/redux.test.js +++ b/src/pages-and-resources/discussions/data/redux.test.js @@ -10,13 +10,7 @@ import { import { fetchApps, saveAppConfig } from './thunks'; import { LOADED } from '../../../data/slice'; import { legacyApiResponse, piazzaApiResponse } from '../factories/mockApiResponses'; - -// Helper, that is used to forcibly finalize all promises -// in thunk before running matcher against state. -const executeThunk = async (thunk, dispatch, getState) => { - await thunk(dispatch, getState); - await new Promise(setImmediate); -}; +import executeThunk from '../../../utils'; const courseId = 'course-v1:edX+TestX+Test_Course'; const pagesAndResourcesPath = `/course/${courseId}/pages-and-resources`; diff --git a/src/pages-and-resources/discussions/factories/mockApiResponses.js b/src/pages-and-resources/discussions/factories/mockApiResponses.js index da8afccb0..a6b8aaac1 100644 --- a/src/pages-and-resources/discussions/factories/mockApiResponses.js +++ b/src/pages-and-resources/discussions/factories/mockApiResponses.js @@ -70,3 +70,18 @@ export const legacyApiResponse = { }, }, }; + +export const emptyAppApiResponse = { + context_key: '', + enabled: null, + provider_type: '', + features: [], + lti_configuration: {}, + plugin_configuration: {}, + providers: { + active: 'legacy', + available: { + + }, + }, +}; diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 000000000..21b8a5580 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,6 @@ +const executeThunk = async (thunk, dispatch, getState) => { + await thunk(dispatch, getState); + await new Promise(setImmediate); +}; + +export default executeThunk;