Compare commits
1 Commits
release/ul
...
jwesson/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3663efb283 |
2
.env
2
.env
@@ -42,5 +42,3 @@ CAREER_LINK_URL=''
|
||||
ENABLE_EDX_PERSONAL_DASHBOARD=false
|
||||
ENABLE_PROGRAMS=false
|
||||
NON_BROWSABLE_COURSES=false
|
||||
# Fallback in local style files
|
||||
PARAGON_THEME_URLS={}
|
||||
|
||||
@@ -48,5 +48,3 @@ CAREER_LINK_URL=''
|
||||
ENABLE_EDX_PERSONAL_DASHBOARD=false
|
||||
ENABLE_PROGRAMS=false
|
||||
NON_BROWSABLE_COURSES=false
|
||||
# Fallback in local style files
|
||||
PARAGON_THEME_URLS={}
|
||||
|
||||
@@ -47,4 +47,3 @@ CAREER_LINK_URL=''
|
||||
ENABLE_EDX_PERSONAL_DASHBOARD=true
|
||||
ENABLE_PROGRAMS=false
|
||||
NON_BROWSABLE_COURSES=false
|
||||
PARAGON_THEME_URLS={}
|
||||
|
||||
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* @openedx/2U-aperture
|
||||
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@@ -14,10 +14,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Nodejs
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
@@ -29,9 +29,6 @@ jobs:
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Type check
|
||||
run: npm run types
|
||||
|
||||
- name: Test
|
||||
run: npm run test
|
||||
|
||||
18
README.rst
18
README.rst
@@ -39,12 +39,22 @@ The parts of this MFE that can be customized in that manner are documented `here
|
||||
Contributing
|
||||
------------
|
||||
|
||||
Contributions are very welcome. Please read `So you want to contribute to Open edX <https://docs.openedx.org/en/latest/developers/quickstarts/so_you_want_to_contribute.html>`_ for details on how to get started as an Open edX contributor.
|
||||
A core goal of this app is to provide a clean experimentation interface. To promote this end, we have provided a
|
||||
silo'ed code directory at ``src/widgets`` in which contributors should add their custom widget components. In order to
|
||||
ensure our ability to maintain the code stability of the app, the code for these widgets should be strictly contained
|
||||
within the bounds of that directory.
|
||||
|
||||
This project is currently accepting all types of contributions — bug fixes, security fixes, maintenance work, or new features.
|
||||
However, if you intend to add a new feature, make sure it has gone through the `Product Review process <https://openedx.atlassian.net/wiki/spaces/COMM/pages/3875962884/How+to+submit+an+open+source+contribution+for+Product+Review>`_.
|
||||
Once written, the widgets can be configured into one of our widget containers at ``src/containers/WidgetContainers``.
|
||||
This can include conditional logic, as well as Optimizely triggers. It is important to note that our integration tests
|
||||
will isolate and ignore these containers, and thus testing your widget is the response of the creator/maintainer of the
|
||||
widget itself.
|
||||
|
||||
When proposing a change, create an issue in this repo to get the discussion started.
|
||||
Some guidelines for writing widgets:
|
||||
|
||||
* Code for the widget should be strictly confined to the ``src/widgets`` directory.
|
||||
* You can load data from the redux store, but should not add or modify fields in that structure.
|
||||
* Network events should be managed in component hooks, though can use our ``data/constants/requests:requestStates`` for
|
||||
ease of tracking the request states.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
@@ -24,7 +24,6 @@ module.exports = {
|
||||
BASE_URL: 'localhost:1996',
|
||||
LMS_BASE_URL: 'http://localhost:18000',
|
||||
ECOMMERCE_BASE_URL: 'http://localhost:18130',
|
||||
CREDIT_PURCHASE_URL: 'http://localhost:8140',
|
||||
LOGIN_URL: 'http://localhost:18000/login',
|
||||
LOGOUT_URL: 'http://localhost:18000/logout',
|
||||
LOGO_URL: 'https://edx-cdn.org/v3/default/logo.svg',
|
||||
|
||||
5316
package-lock.json
generated
5316
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@@ -20,7 +20,7 @@
|
||||
"test": "TZ=GMT fedx-scripts jest --coverage --passWithNoTests",
|
||||
"quality": "npm run lint-fix && npm run test",
|
||||
"watch-tests": "jest --watch",
|
||||
"types": "tsc --noEmit"
|
||||
"snapshot": "fedx-scripts jest --updateSnapshot"
|
||||
},
|
||||
"author": "edX",
|
||||
"license": "AGPL-3.0",
|
||||
@@ -31,20 +31,21 @@
|
||||
"dependencies": {
|
||||
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
|
||||
"@edx/frontend-component-footer": "^14.6.0",
|
||||
"@edx/frontend-component-header": "^6.6.0",
|
||||
"@edx/frontend-component-header": "^6.2.0",
|
||||
"@edx/frontend-enterprise-hotjar": "7.2.0",
|
||||
"@edx/frontend-platform": "^8.3.1",
|
||||
"@edx/openedx-atlas": "^0.7.0",
|
||||
"@edx/react-unit-test-utils": "^4.0.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@openedx/frontend-plugin-framework": "^1.7.0",
|
||||
"@openedx/paragon": "^23.4.5",
|
||||
"@openedx/paragon": "^22.16.0",
|
||||
"@redux-devtools/extension": "3.3.0",
|
||||
"@reduxjs/toolkit": "^2.0.0",
|
||||
"classnames": "^2.3.1",
|
||||
"core-js": "3.46.0",
|
||||
"core-js": "3.42.0",
|
||||
"filesize": "^10.0.0",
|
||||
"font-awesome": "4.7.0",
|
||||
"history": "5.3.0",
|
||||
@@ -56,7 +57,7 @@
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-intl": "6.8.9",
|
||||
"react-redux": "^7.2.4",
|
||||
"react-router-dom": "6.30.1",
|
||||
"react-router-dom": "6.29.0",
|
||||
"react-share": "^4.4.0",
|
||||
"redux": "4.2.1",
|
||||
"redux-logger": "3.0.6",
|
||||
@@ -68,12 +69,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@edx/browserslist-config": "^1.3.0",
|
||||
"@edx/typescript-config": "^1.1.0",
|
||||
"@openedx/frontend-build": "^14.6.2",
|
||||
"@edx/reactifex": "^2.1.1",
|
||||
"@openedx/frontend-build": "^14.3.3",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"copy-webpack-plugin": "^13.0.0",
|
||||
"copy-webpack-plugin": "^12.0.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
|
||||
@@ -22,7 +22,7 @@ import track from 'tracking';
|
||||
|
||||
import fakeData from 'data/services/lms/fakeData/courses';
|
||||
|
||||
import AppWrapper from 'containers/AppWrapper';
|
||||
import AppWrapper from 'containers/WidgetContainers/AppWrapper';
|
||||
import LearnerDashboardHeader from 'containers/LearnerDashboardHeader';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
// frontend-app-*/src/index.scss
|
||||
@use "@openedx/paragon/styles/css/core/custom-media-breakpoints" as paragonCustomMediaBreakpoints;
|
||||
@import "~@edx/brand/paragon/fonts";
|
||||
@import "~@edx/brand/paragon/variables";
|
||||
@import "~@openedx/paragon/scss/core/core";
|
||||
@import "~@edx/brand/paragon/overrides";
|
||||
|
||||
$fa-font-path: "~font-awesome/fonts";
|
||||
@import "~font-awesome/scss/font-awesome";
|
||||
|
||||
$input-focus-box-shadow: var(--pgn-elevation-form-input-base); // hack to get upgrade to paragon 4.0.0 to work
|
||||
$input-focus-box-shadow: $input-box-shadow; // hack to get upgrade to paragon 4.0.0 to work
|
||||
|
||||
@import "~@edx/frontend-component-header/dist/index";
|
||||
@import "~@edx/frontend-component-footer/dist/_footer";
|
||||
|
||||
120
src/App.test.jsx
120
src/App.test.jsx
@@ -1,19 +1,23 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
|
||||
import { RequestKeys } from 'data/constants/requests';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import Dashboard from 'containers/Dashboard';
|
||||
import LearnerDashboardHeader from 'containers/LearnerDashboardHeader';
|
||||
import AppWrapper from 'containers/WidgetContainers/AppWrapper';
|
||||
import { App } from './App';
|
||||
import messages from './messages';
|
||||
|
||||
jest.mock('@edx/frontend-component-footer', () => ({
|
||||
FooterSlot: jest.fn(() => <div>FooterSlot</div>),
|
||||
}));
|
||||
jest.mock('containers/Dashboard', () => jest.fn(() => <div>Dashboard</div>));
|
||||
jest.mock('containers/LearnerDashboardHeader', () => jest.fn(() => <div>LearnerDashboardHeader</div>));
|
||||
jest.mock('containers/AppWrapper', () => jest.fn(({ children }) => <div className="AppWrapper">{children}</div>));
|
||||
jest.mock('@edx/frontend-component-footer', () => ({ FooterSlot: 'FooterSlot' }));
|
||||
|
||||
jest.mock('containers/Dashboard', () => 'Dashboard');
|
||||
jest.mock('containers/LearnerDashboardHeader', () => 'LearnerDashboardHeader');
|
||||
jest.mock('containers/WidgetContainers/AppWrapper', () => 'AppWrapper');
|
||||
jest.mock('data/redux', () => ({
|
||||
selectors: 'redux.selectors',
|
||||
actions: 'redux.actions',
|
||||
@@ -32,103 +36,113 @@ jest.mock('@edx/frontend-platform', () => ({
|
||||
getConfig: jest.fn(() => ({})),
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-platform/react', () => ({
|
||||
...jest.requireActual('@edx/frontend-platform/react'),
|
||||
ErrorPage: () => 'ErrorPage',
|
||||
}));
|
||||
|
||||
const loadData = jest.fn();
|
||||
reduxHooks.useLoadData.mockReturnValue(loadData);
|
||||
|
||||
const supportEmail = 'test@support.com';
|
||||
let el;
|
||||
|
||||
const supportEmail = 'test-support-url';
|
||||
reduxHooks.usePlatformSettingsData.mockReturnValue({ supportEmail });
|
||||
|
||||
describe('App router component', () => {
|
||||
const { formatMessage } = useIntl();
|
||||
describe('component', () => {
|
||||
const runBasicTests = () => {
|
||||
it('displays title in helmet component', async () => {
|
||||
await waitFor(() => expect(document.title).toEqual(messages.pageTitle.defaultMessage));
|
||||
test('snapshot', () => { expect(el.snapshot).toMatchSnapshot(); });
|
||||
it('displays title in helmet component', () => {
|
||||
const control = el.instance
|
||||
.findByType(Helmet)[0]
|
||||
.findByType('title')[0];
|
||||
expect(control.children[0].el).toEqual(formatMessage(messages.pageTitle));
|
||||
});
|
||||
it('displays learner dashboard header', () => {
|
||||
const learnerDashboardHeader = screen.getByText('LearnerDashboardHeader');
|
||||
expect(learnerDashboardHeader).toBeInTheDocument();
|
||||
expect(el.instance.findByType(LearnerDashboardHeader).length).toEqual(1);
|
||||
});
|
||||
it('wraps the header and main components in an AppWrapper widget container', () => {
|
||||
const appWrapper = screen.getByText('LearnerDashboardHeader').parentElement;
|
||||
expect(appWrapper).toHaveClass('AppWrapper');
|
||||
expect(appWrapper.children[1].id).toEqual('main');
|
||||
});
|
||||
it('displays footer slot', () => {
|
||||
const footerSlot = screen.getByText('FooterSlot');
|
||||
expect(footerSlot).toBeInTheDocument();
|
||||
const container = el.instance.findByType(AppWrapper)[0];
|
||||
expect(container.children[0].type).toEqual('LearnerDashboardHeader');
|
||||
expect(container.children[1].type).toEqual('main');
|
||||
});
|
||||
};
|
||||
describe('no network failure', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
beforeAll(() => {
|
||||
reduxHooks.useRequestIsFailed.mockReturnValue(false);
|
||||
getConfig.mockReturnValue({});
|
||||
render(<IntlProvider locale="en"><App /></IntlProvider>);
|
||||
el = shallow(<App />);
|
||||
});
|
||||
runBasicTests();
|
||||
it('loads dashboard', () => {
|
||||
const dashboard = screen.getByText('Dashboard');
|
||||
expect(dashboard).toBeInTheDocument();
|
||||
const main = el.instance.findByType('main')[0];
|
||||
expect(main.children.length).toEqual(1);
|
||||
const dashboard = main.children[0].el;
|
||||
expect(dashboard.type).toEqual('Dashboard');
|
||||
expect(dashboard).toEqual(shallow(<Dashboard />));
|
||||
});
|
||||
});
|
||||
describe('no network failure with optimizely url', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
beforeAll(() => {
|
||||
reduxHooks.useRequestIsFailed.mockReturnValue(false);
|
||||
getConfig.mockReturnValue({ OPTIMIZELY_URL: 'fake.url' });
|
||||
render(<IntlProvider locale="en"><App /></IntlProvider>);
|
||||
el = shallow(<App />);
|
||||
});
|
||||
runBasicTests();
|
||||
it('loads dashboard', () => {
|
||||
const dashboard = screen.getByText('Dashboard');
|
||||
expect(dashboard).toBeInTheDocument();
|
||||
const main = el.instance.findByType('main')[0];
|
||||
expect(main.children.length).toEqual(1);
|
||||
const dashboard = main.children[0].el;
|
||||
expect(dashboard.type).toEqual('Dashboard');
|
||||
expect(dashboard).toEqual(shallow(<Dashboard />));
|
||||
});
|
||||
});
|
||||
describe('no network failure with optimizely project id', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
beforeAll(() => {
|
||||
reduxHooks.useRequestIsFailed.mockReturnValue(false);
|
||||
getConfig.mockReturnValue({ OPTIMIZELY_PROJECT_ID: 'fakeId' });
|
||||
render(<IntlProvider locale="en"><App /></IntlProvider>);
|
||||
el = shallow(<App />);
|
||||
});
|
||||
runBasicTests();
|
||||
it('loads dashboard', () => {
|
||||
const dashboard = screen.getByText('Dashboard');
|
||||
expect(dashboard).toBeInTheDocument();
|
||||
const main = el.instance.findByType('main')[0];
|
||||
expect(main.children.length).toEqual(1);
|
||||
const dashboard = main.children[0].el;
|
||||
expect(dashboard.type).toEqual('Dashboard');
|
||||
expect(dashboard).toEqual(shallow(<Dashboard />));
|
||||
});
|
||||
});
|
||||
describe('initialize failure', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
beforeAll(() => {
|
||||
reduxHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.initialize);
|
||||
getConfig.mockReturnValue({});
|
||||
render(<IntlProvider locale="en" messages={messages}><App /></IntlProvider>);
|
||||
el = shallow(<App />);
|
||||
});
|
||||
runBasicTests();
|
||||
it('loads error page', () => {
|
||||
const alert = screen.getByRole('alert');
|
||||
expect(alert).toBeInTheDocument();
|
||||
const errorPage = screen.getByText('ErrorPage');
|
||||
expect(errorPage).toBeInTheDocument();
|
||||
const main = el.instance.findByType('main')[0];
|
||||
expect(main.children.length).toEqual(1);
|
||||
const alert = main.children[0];
|
||||
expect(alert.type).toEqual('Alert');
|
||||
expect(alert.children.length).toEqual(1);
|
||||
const errorPage = alert.children[0];
|
||||
expect(errorPage.type).toEqual('ErrorPage');
|
||||
expect(errorPage.props.message).toEqual(formatMessage(messages.errorMessage, { supportEmail }));
|
||||
});
|
||||
});
|
||||
describe('refresh failure', () => {
|
||||
beforeEach(() => {
|
||||
beforeAll(() => {
|
||||
reduxHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.refreshList);
|
||||
getConfig.mockReturnValue({});
|
||||
render(<IntlProvider locale="en"><App /></IntlProvider>);
|
||||
el = shallow(<App />);
|
||||
});
|
||||
runBasicTests();
|
||||
it('loads error page', () => {
|
||||
const alert = screen.getByRole('alert');
|
||||
expect(alert).toBeInTheDocument();
|
||||
const errorPage = screen.getByText('ErrorPage');
|
||||
expect(errorPage).toBeInTheDocument();
|
||||
const main = el.instance.findByType('main')[0];
|
||||
expect(main.children.length).toEqual(1);
|
||||
const alert = main.children[0];
|
||||
expect(alert.type).toEqual('Alert');
|
||||
expect(alert.children.length).toEqual(1);
|
||||
const errorPage = alert.children[0];
|
||||
expect(errorPage.type).toEqual('ErrorPage');
|
||||
expect(errorPage.props.message).toEqual(formatMessage(messages.errorMessage, { supportEmail }));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
153
src/__snapshots__/App.test.jsx.snap
Normal file
153
src/__snapshots__/App.test.jsx.snap
Normal file
@@ -0,0 +1,153 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`App router component component initialize failure snapshot 1`] = `
|
||||
<Fragment>
|
||||
<HelmetWrapper
|
||||
defer={true}
|
||||
encodeSpecialCharacters={true}
|
||||
>
|
||||
<title>
|
||||
Learner Home
|
||||
</title>
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
type="image/x-icon"
|
||||
/>
|
||||
</HelmetWrapper>
|
||||
<div>
|
||||
<AppWrapper>
|
||||
<LearnerDashboardHeader />
|
||||
<main
|
||||
id="main"
|
||||
>
|
||||
<Alert
|
||||
variant="danger"
|
||||
>
|
||||
<ErrorPage
|
||||
message="If you experience repeated failures, please email support at test-support-url"
|
||||
/>
|
||||
</Alert>
|
||||
</main>
|
||||
</AppWrapper>
|
||||
<FooterSlot />
|
||||
</div>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`App router component component no network failure snapshot 1`] = `
|
||||
<Fragment>
|
||||
<HelmetWrapper
|
||||
defer={true}
|
||||
encodeSpecialCharacters={true}
|
||||
>
|
||||
<title>
|
||||
Learner Home
|
||||
</title>
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
type="image/x-icon"
|
||||
/>
|
||||
</HelmetWrapper>
|
||||
<div>
|
||||
<AppWrapper>
|
||||
<LearnerDashboardHeader />
|
||||
<main
|
||||
id="main"
|
||||
>
|
||||
<Dashboard />
|
||||
</main>
|
||||
</AppWrapper>
|
||||
<FooterSlot />
|
||||
</div>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`App router component component no network failure with optimizely project id snapshot 1`] = `
|
||||
<Fragment>
|
||||
<HelmetWrapper
|
||||
defer={true}
|
||||
encodeSpecialCharacters={true}
|
||||
>
|
||||
<title>
|
||||
Learner Home
|
||||
</title>
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
type="image/x-icon"
|
||||
/>
|
||||
</HelmetWrapper>
|
||||
<div>
|
||||
<AppWrapper>
|
||||
<LearnerDashboardHeader />
|
||||
<main
|
||||
id="main"
|
||||
>
|
||||
<Dashboard />
|
||||
</main>
|
||||
</AppWrapper>
|
||||
<FooterSlot />
|
||||
</div>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`App router component component no network failure with optimizely url snapshot 1`] = `
|
||||
<Fragment>
|
||||
<HelmetWrapper
|
||||
defer={true}
|
||||
encodeSpecialCharacters={true}
|
||||
>
|
||||
<title>
|
||||
Learner Home
|
||||
</title>
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
type="image/x-icon"
|
||||
/>
|
||||
</HelmetWrapper>
|
||||
<div>
|
||||
<AppWrapper>
|
||||
<LearnerDashboardHeader />
|
||||
<main
|
||||
id="main"
|
||||
>
|
||||
<Dashboard />
|
||||
</main>
|
||||
</AppWrapper>
|
||||
<FooterSlot />
|
||||
</div>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`App router component component refresh failure snapshot 1`] = `
|
||||
<Fragment>
|
||||
<HelmetWrapper
|
||||
defer={true}
|
||||
encodeSpecialCharacters={true}
|
||||
>
|
||||
<title>
|
||||
Learner Home
|
||||
</title>
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
type="image/x-icon"
|
||||
/>
|
||||
</HelmetWrapper>
|
||||
<div>
|
||||
<AppWrapper>
|
||||
<LearnerDashboardHeader />
|
||||
<main
|
||||
id="main"
|
||||
>
|
||||
<Alert
|
||||
variant="danger"
|
||||
>
|
||||
<ErrorPage
|
||||
message="If you experience repeated failures, please email support at test-support-url"
|
||||
/>
|
||||
</Alert>
|
||||
</main>
|
||||
</AppWrapper>
|
||||
<FooterSlot />
|
||||
</div>
|
||||
</Fragment>
|
||||
`;
|
||||
43
src/__snapshots__/index.test.jsx.snap
Normal file
43
src/__snapshots__/index.test.jsx.snap
Normal file
@@ -0,0 +1,43 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`app registry subscribe: APP_INIT_ERROR. snapshot: displays an ErrorPage to root element 1`] = `
|
||||
<UNDEFINED>
|
||||
<ErrorPage
|
||||
message="test-error-message"
|
||||
/>
|
||||
</UNDEFINED>
|
||||
`;
|
||||
|
||||
exports[`app registry subscribe: APP_READY. links App to root element 1`] = `
|
||||
<UNDEFINED>
|
||||
<AppProvider
|
||||
store={
|
||||
{
|
||||
"redux": "store",
|
||||
}
|
||||
}
|
||||
>
|
||||
<NoticesWrapper>
|
||||
<Routes>
|
||||
<Route
|
||||
element={
|
||||
<PageWrap>
|
||||
<App />
|
||||
</PageWrap>
|
||||
}
|
||||
path="/"
|
||||
/>
|
||||
<Route
|
||||
element={
|
||||
<Navigate
|
||||
replace={true}
|
||||
to="/"
|
||||
/>
|
||||
}
|
||||
path="*"
|
||||
/>
|
||||
</Routes>
|
||||
</NoticesWrapper>
|
||||
</AppProvider>
|
||||
</UNDEFINED>
|
||||
`;
|
||||
@@ -1,27 +1,27 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { Alert } from '@openedx/paragon';
|
||||
|
||||
import Banner from './Banner';
|
||||
|
||||
describe('Banner component', () => {
|
||||
it('renders children content', () => {
|
||||
render(<Banner>Test content</Banner>);
|
||||
expect(screen.getByText('Test content')).toBeInTheDocument();
|
||||
});
|
||||
describe('Banner', () => {
|
||||
const props = {
|
||||
children: 'Hello, world!',
|
||||
};
|
||||
describe('snapshot', () => {
|
||||
test('renders default banner', () => {
|
||||
const wrapper = shallow(<Banner {...props} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
test('renders with variants', () => {
|
||||
const wrapper = shallow(<Banner {...props} variant="success" />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
|
||||
it('uses default props correctly', () => {
|
||||
render(<Banner>Test content</Banner>);
|
||||
const banner = screen.getByRole('alert');
|
||||
expect(banner).toHaveClass('mb-0');
|
||||
});
|
||||
|
||||
it('accepts custom variant prop', () => {
|
||||
render(<Banner variant="success">Test content</Banner>);
|
||||
const banner = screen.getByRole('alert');
|
||||
expect(banner).toHaveClass('alert-success');
|
||||
});
|
||||
|
||||
it('accepts custom className prop', () => {
|
||||
render(<Banner className="custom-class">Test content</Banner>);
|
||||
const banner = screen.getByRole('alert');
|
||||
expect(banner).toHaveClass('custom-class');
|
||||
expect(wrapper.instance.findByType(Alert)[0].props.variant).toEqual('success');
|
||||
});
|
||||
test('renders with custom class', () => {
|
||||
const wrapper = shallow(<Banner {...props} className="custom-class" />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import { StrictDict } from 'utils';
|
||||
import { getNotices } from './api';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import { MockUseState, formatMessage } from 'testUtils';
|
||||
import { MockUseState } from 'testUtils';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getNotices } from './api';
|
||||
@@ -8,23 +8,13 @@ import * as hooks from './hooks';
|
||||
|
||||
jest.mock('@edx/frontend-platform', () => ({ getConfig: jest.fn() }));
|
||||
jest.mock('./api', () => ({ getNotices: jest.fn() }));
|
||||
|
||||
jest.mock('react', () => ({
|
||||
...jest.requireActual('react'),
|
||||
useEffect: jest.fn((cb, prereqs) => ({ useEffect: { cb, prereqs } })),
|
||||
useContext: jest.fn(context => context),
|
||||
const mockFormatMessage = jest.fn(message => message.defaultMessage || 'translated-string');
|
||||
jest.mock('react-intl', () => ({
|
||||
useIntl: () => ({
|
||||
formatMessage: mockFormatMessage,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-platform/i18n', () => {
|
||||
const { formatMessage: fn } = jest.requireActual('testUtils');
|
||||
return {
|
||||
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||
useIntl: () => ({
|
||||
formatMessage: fn,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
getConfig.mockReturnValue({ ENABLE_NOTICES: true });
|
||||
const state = new MockUseState(hooks);
|
||||
|
||||
@@ -50,7 +40,7 @@ describe('NoticesWrapper hooks', () => {
|
||||
getConfig.mockReturnValueOnce({ ENABLE_NOTICES: false });
|
||||
hooks.useNoticesWrapperData();
|
||||
const [cb, prereqs] = React.useEffect.mock.calls[0];
|
||||
expect(prereqs).toEqual([state.setState.isRedirected, formatMessage]);
|
||||
expect(prereqs).toEqual([state.setState.isRedirected, mockFormatMessage]);
|
||||
cb();
|
||||
expect(getNotices).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -59,7 +49,7 @@ describe('NoticesWrapper hooks', () => {
|
||||
hooks.useNoticesWrapperData();
|
||||
expect(React.useEffect).toHaveBeenCalled();
|
||||
const [cb, prereqs] = React.useEffect.mock.calls[0];
|
||||
expect(prereqs).toEqual([state.setState.isRedirected, formatMessage]);
|
||||
expect(prereqs).toEqual([state.setState.isRedirected, mockFormatMessage]);
|
||||
cb();
|
||||
expect(getNotices).toHaveBeenCalled();
|
||||
const { onLoad } = getNotices.mock.calls[0][0];
|
||||
@@ -75,7 +65,7 @@ describe('NoticesWrapper hooks', () => {
|
||||
window.location = { replace: jest.fn(), href: 'test-old-href' };
|
||||
hooks.useNoticesWrapperData();
|
||||
const [cb, prereqs] = React.useEffect.mock.calls[0];
|
||||
expect(prereqs).toEqual([state.setState.isRedirected, formatMessage]);
|
||||
expect(prereqs).toEqual([state.setState.isRedirected, mockFormatMessage]);
|
||||
cb();
|
||||
expect(getNotices).toHaveBeenCalled();
|
||||
const { onLoad } = getNotices.mock.calls[0][0];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import useNoticesWrapperData from './hooks';
|
||||
import NoticesWrapper from '.';
|
||||
@@ -6,31 +7,30 @@ import NoticesWrapper from '.';
|
||||
jest.mock('./hooks', () => jest.fn());
|
||||
|
||||
const hookProps = { isRedirected: false };
|
||||
useNoticesWrapperData.mockReturnValue(hookProps);
|
||||
|
||||
let el;
|
||||
const children = [<b key={1}>some</b>, <i key={2}>children</i>];
|
||||
describe('NoticesWrapper component', () => {
|
||||
beforeEach(() => {
|
||||
useNoticesWrapperData.mockClear();
|
||||
});
|
||||
describe('behavior', () => {
|
||||
it('initializes hooks', () => {
|
||||
useNoticesWrapperData.mockReturnValue(hookProps);
|
||||
render(<NoticesWrapper>{children}</NoticesWrapper>);
|
||||
el = shallow(<NoticesWrapper>{children}</NoticesWrapper>);
|
||||
expect(useNoticesWrapperData).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
describe('output', () => {
|
||||
it('does not show children if redirected', () => {
|
||||
useNoticesWrapperData.mockReturnValueOnce({ isRedirected: true });
|
||||
render(<NoticesWrapper>{children}</NoticesWrapper>);
|
||||
expect(screen.queryByText('some')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('children')).not.toBeInTheDocument();
|
||||
el = shallow(<NoticesWrapper>{children}</NoticesWrapper>);
|
||||
expect(el.instance.children.length).toEqual(0);
|
||||
});
|
||||
it('shows children if not redirected', () => {
|
||||
useNoticesWrapperData.mockReturnValue(hookProps);
|
||||
render(<NoticesWrapper>{children}</NoticesWrapper>);
|
||||
expect(screen.getByText('some')).toBeInTheDocument();
|
||||
expect(screen.getByText('children')).toBeInTheDocument();
|
||||
el = shallow(<NoticesWrapper>{children}</NoticesWrapper>);
|
||||
expect(el.instance.children.length).toEqual(2);
|
||||
expect(el.instance.children[0].type).toEqual(shallow(children[0]).type);
|
||||
expect(el.instance.props).toEqual(shallow(children[0]).props);
|
||||
expect(el.instance.children[1].type).toEqual(shallow(children[1]).type);
|
||||
expect(el.instance.props).toEqual(shallow(children[1]).props);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
31
src/components/__snapshots__/Banner.test.jsx.snap
Normal file
31
src/components/__snapshots__/Banner.test.jsx.snap
Normal file
@@ -0,0 +1,31 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Banner snapshot renders default banner 1`] = `
|
||||
<Alert
|
||||
className="mb-0"
|
||||
icon={[MockFunction icons.Info]}
|
||||
variant="info"
|
||||
>
|
||||
Hello, world!
|
||||
</Alert>
|
||||
`;
|
||||
|
||||
exports[`Banner snapshot renders with custom class 1`] = `
|
||||
<Alert
|
||||
className="custom-class"
|
||||
icon={[MockFunction icons.Info]}
|
||||
variant="info"
|
||||
>
|
||||
Hello, world!
|
||||
</Alert>
|
||||
`;
|
||||
|
||||
exports[`Banner snapshot renders with variants 1`] = `
|
||||
<Alert
|
||||
className="mb-0"
|
||||
icon={[MockFunction icons.Info]}
|
||||
variant="success"
|
||||
>
|
||||
Hello, world!
|
||||
</Alert>
|
||||
`;
|
||||
@@ -2,7 +2,6 @@ const configuration = {
|
||||
// BASE_URL: process.env.BASE_URL,
|
||||
LMS_BASE_URL: process.env.LMS_BASE_URL,
|
||||
ECOMMERCE_BASE_URL: process.env.ECOMMERCE_BASE_URL,
|
||||
CREDIT_PURCHASE_URL: process.env.CREDIT_PURCHASE_URL,
|
||||
// LOGIN_URL: process.env.LOGIN_URL,
|
||||
// LOGOUT_URL: process.env.LOGOUT_URL,
|
||||
// CSRF_TOKEN_API_PATH: process.env.CSRF_TOKEN_API_PATH,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
@import "@openedx/paragon/scss/core/core";
|
||||
|
||||
.course-card {
|
||||
.card {
|
||||
.pgn__card-wrapper-image-cap.vertical {
|
||||
display: flex;
|
||||
min-height: var(--pgn-size-card-image-vertical-max-height);
|
||||
min-height: $card-image-vertical-max-height;
|
||||
}
|
||||
.pgn__card-image-cap {
|
||||
border-bottom-left-radius: 0 !important;
|
||||
@@ -51,11 +53,11 @@
|
||||
> .alert {
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
padding: var(--pgn-spacing-spacer-3) var(--pgn-spacing-spacer-4);
|
||||
padding: map-get($spacers, 3) map-get($spacers, 4);
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom-left-radius: var(--pgn-size-alert-border-radius);
|
||||
border-bottom-right-radius: var(--pgn-size-alert-border-radius);
|
||||
border-bottom-left-radius: $alert-border-radius;
|
||||
border-bottom-right-radius: $alert-border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
111
src/containers/CourseCard/__snapshots__/index.test.jsx.snap
Normal file
111
src/containers/CourseCard/__snapshots__/index.test.jsx.snap
Normal file
@@ -0,0 +1,111 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CourseCard component snapshot: collapsed 1`] = `
|
||||
<div
|
||||
className="mb-4.5 course-card"
|
||||
data-testid="CourseCard"
|
||||
id="test-card-id"
|
||||
>
|
||||
<Card
|
||||
orientation="vertical"
|
||||
>
|
||||
<div
|
||||
className="d-flex flex-column w-100"
|
||||
>
|
||||
<div>
|
||||
<CourseCardImage
|
||||
cardId="test-card-id"
|
||||
orientation="horizontal"
|
||||
/>
|
||||
<Card.Body>
|
||||
<Card.Header
|
||||
actions={
|
||||
<CourseCardMenu
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<CourseCardTitle
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Card.Section
|
||||
className="pt-0"
|
||||
>
|
||||
<CourseCardDetails
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
</Card.Section>
|
||||
<Card.Footer
|
||||
orientation="vertical"
|
||||
>
|
||||
<CourseCardActions
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
</Card.Footer>
|
||||
</Card.Body>
|
||||
</div>
|
||||
<CourseCardBanners
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`CourseCard component snapshot: not collapsed 1`] = `
|
||||
<div
|
||||
className="mb-4.5 course-card"
|
||||
data-testid="CourseCard"
|
||||
id="test-card-id"
|
||||
>
|
||||
<Card
|
||||
orientation="horizontal"
|
||||
>
|
||||
<div
|
||||
className="d-flex flex-column w-100"
|
||||
>
|
||||
<div
|
||||
className="d-flex"
|
||||
>
|
||||
<CourseCardImage
|
||||
cardId="test-card-id"
|
||||
orientation="horizontal"
|
||||
/>
|
||||
<Card.Body>
|
||||
<Card.Header
|
||||
actions={
|
||||
<CourseCardMenu
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<CourseCardTitle
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Card.Section
|
||||
className="pt-0"
|
||||
>
|
||||
<CourseCardDetails
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
</Card.Section>
|
||||
<Card.Footer
|
||||
orientation="horizontal"
|
||||
>
|
||||
<CourseCardActions
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
</Card.Footer>
|
||||
</Card.Body>
|
||||
</div>
|
||||
<CourseCardBanners
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,14 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ActionButton snapshot is collapsed 1`] = `
|
||||
<Button
|
||||
arbitary="props"
|
||||
size="sm"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`ActionButton snapshot is not collapsed 1`] = `
|
||||
<Button
|
||||
arbitary="props"
|
||||
/>
|
||||
`;
|
||||
@@ -1,33 +1,6 @@
|
||||
import { useWindowSize, breakpoints } from '@openedx/paragon';
|
||||
import useIsCollapsed from './hooks';
|
||||
|
||||
jest.mock('@openedx/paragon', () => ({
|
||||
...jest.requireActual('@openedx/paragon'),
|
||||
useWindowSize: jest.fn(),
|
||||
breakpoints: {
|
||||
extraSmall: {
|
||||
minWidth: 0,
|
||||
maxWidth: 575,
|
||||
},
|
||||
small: {
|
||||
minWidth: 576,
|
||||
maxWidth: 767,
|
||||
},
|
||||
medium: {
|
||||
minWidth: 768,
|
||||
maxWidth: 991,
|
||||
},
|
||||
large: {
|
||||
minWidth: 992,
|
||||
maxWidth: 1199,
|
||||
},
|
||||
extraLarge: {
|
||||
minWidth: 1200,
|
||||
maxWidth: 100000,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe('useIsCollapsed', () => {
|
||||
it('returns true only when it is between medium and small', () => {
|
||||
// make sure all three breakpoints gap is large enough for test
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import ActionButton from '.';
|
||||
|
||||
import useIsCollapsed from './hooks';
|
||||
@@ -7,22 +8,18 @@ jest.mock('./hooks', () => jest.fn());
|
||||
|
||||
describe('ActionButton', () => {
|
||||
const props = {
|
||||
className: 'custom-class',
|
||||
children: 'Test',
|
||||
arbitary: 'props',
|
||||
};
|
||||
|
||||
it('is collapsed', async () => {
|
||||
useIsCollapsed.mockReturnValue(true);
|
||||
render(<ActionButton {...props} />);
|
||||
const button = screen.getByRole('button', { name: 'Test' });
|
||||
expect(button).toHaveClass('btn-sm', 'custom-class');
|
||||
});
|
||||
|
||||
it('is not collapsed', () => {
|
||||
useIsCollapsed.mockReturnValue(false);
|
||||
render(<ActionButton {...props} />);
|
||||
const button = screen.getByRole('button', { name: 'Test' });
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).not.toHaveClass('size', 'sm');
|
||||
describe('snapshot', () => {
|
||||
test('is collapsed', () => {
|
||||
useIsCollapsed.mockReturnValueOnce(true);
|
||||
const wrapper = shallow(<ActionButton {...props} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
test('is not collapsed', () => {
|
||||
useIsCollapsed.mockReturnValueOnce(false);
|
||||
const wrapper = shallow(<ActionButton {...props} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import track from 'tracking';
|
||||
import useActionDisabledState from '../hooks';
|
||||
@@ -19,11 +18,10 @@ jest.mock('hooks', () => ({
|
||||
useTrackCourseEvent: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../hooks', () => jest.fn(() => ({ disableBeginCourse: false })));
|
||||
jest.mock('./ActionButton', () => 'ActionButton');
|
||||
|
||||
jest.mock('./ActionButton/hooks', () => jest.fn(() => false));
|
||||
|
||||
let wrapper;
|
||||
const homeUrl = 'home-url';
|
||||
reduxHooks.useCardCourseRunData.mockReturnValue({ homeUrl });
|
||||
const execEdPath = (cardId) => `exec-ed-tracking-path=${cardId}`;
|
||||
@@ -32,57 +30,56 @@ reduxHooks.useTrackCourseEvent.mockImplementation(
|
||||
(eventName, cardId, url) => ({ trackCourseEvent: { eventName, cardId, url } }),
|
||||
);
|
||||
|
||||
const props = {
|
||||
cardId: 'cardId',
|
||||
};
|
||||
|
||||
const renderComponent = () => render(<IntlProvider locale="en"><BeginCourseButton {...props} /></IntlProvider>);
|
||||
|
||||
describe('BeginCourseButton', () => {
|
||||
const props = {
|
||||
cardId: 'cardId',
|
||||
};
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
describe('initiliaze hooks', () => {
|
||||
describe('behavior', () => {
|
||||
it('initializes course run data with cardId', () => {
|
||||
renderComponent();
|
||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
||||
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(props.cardId);
|
||||
});
|
||||
it('loads exec education path param', () => {
|
||||
renderComponent();
|
||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
||||
expect(reduxHooks.useCardExecEdTrackingParam).toHaveBeenCalledWith(props.cardId);
|
||||
});
|
||||
it('loads disabled states for begin action from action hooks', () => {
|
||||
renderComponent();
|
||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
||||
expect(useActionDisabledState).toHaveBeenCalledWith(props.cardId);
|
||||
});
|
||||
});
|
||||
describe('behavior', () => {
|
||||
describe('snapshot', () => {
|
||||
describe('disabled', () => {
|
||||
it('should be disabled', () => {
|
||||
beforeEach(() => {
|
||||
useActionDisabledState.mockReturnValueOnce({ disableBeginCourse: true });
|
||||
renderComponent();
|
||||
const button = screen.getByRole('button', { name: 'Begin Course' });
|
||||
expect(button).toHaveClass('disabled');
|
||||
expect(button).toHaveAttribute('aria-disabled', 'true');
|
||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
||||
});
|
||||
test('snapshot', () => {
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
it('should be disabled', () => {
|
||||
expect(wrapper.instance.props.disabled).toEqual(true);
|
||||
});
|
||||
});
|
||||
describe('enabled', () => {
|
||||
it('should be enabled', () => {
|
||||
renderComponent();
|
||||
const button = screen.getByRole('button', { name: 'Begin Course' });
|
||||
expect(button).not.toHaveClass('disabled');
|
||||
expect(button).not.toHaveAttribute('aria-disabled', 'true');
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
||||
});
|
||||
it('should track enter course clicked event on click, with exec ed param', async () => {
|
||||
renderComponent();
|
||||
const user = userEvent.setup();
|
||||
const button = screen.getByRole('button', { name: 'Begin Course' });
|
||||
user.click(button);
|
||||
expect(reduxHooks.useTrackCourseEvent).toHaveBeenCalledWith(
|
||||
test('snapshot', () => {
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
it('should be enabled', () => {
|
||||
expect(wrapper.instance.props.disabled).toEqual(false);
|
||||
});
|
||||
it('should track enter course clicked event on click, with exec ed param', () => {
|
||||
expect(wrapper.instance.props.onClick).toEqual(reduxHooks.useTrackCourseEvent(
|
||||
track.course.enterCourseClicked,
|
||||
props.cardId,
|
||||
homeUrl + execEdPath(props.cardId),
|
||||
);
|
||||
));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import track from 'tracking';
|
||||
@@ -21,8 +19,7 @@ jest.mock('hooks', () => ({
|
||||
},
|
||||
}));
|
||||
jest.mock('../hooks', () => jest.fn(() => ({ disableResumeCourse: false })));
|
||||
|
||||
jest.mock('./ActionButton/hooks', () => jest.fn(() => false));
|
||||
jest.mock('./ActionButton', () => 'ActionButton');
|
||||
|
||||
const resumeUrl = 'resume-url';
|
||||
reduxHooks.useCardCourseRunData.mockReturnValue({ resumeUrl });
|
||||
@@ -32,52 +29,55 @@ reduxHooks.useTrackCourseEvent.mockImplementation(
|
||||
(eventName, cardId, url) => ({ trackCourseEvent: { eventName, cardId, url } }),
|
||||
);
|
||||
|
||||
let wrapper;
|
||||
|
||||
describe('ResumeButton', () => {
|
||||
const props = {
|
||||
cardId: 'cardId',
|
||||
};
|
||||
describe('initialize hooks', () => {
|
||||
beforeEach(() => render(<IntlProvider locale="en"><ResumeButton {...props} /></IntlProvider>));
|
||||
describe('behavior', () => {
|
||||
it('initializes course run data with cardId', () => {
|
||||
wrapper = shallow(<ResumeButton {...props} />);
|
||||
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(props.cardId);
|
||||
});
|
||||
it('loads exec education path param', () => {
|
||||
wrapper = shallow(<ResumeButton {...props} />);
|
||||
expect(reduxHooks.useCardExecEdTrackingParam).toHaveBeenCalledWith(props.cardId);
|
||||
});
|
||||
it('loads disabled states for resume action from action hooks', () => {
|
||||
wrapper = shallow(<ResumeButton {...props} />);
|
||||
expect(useActionDisabledState).toHaveBeenCalledWith(props.cardId);
|
||||
});
|
||||
});
|
||||
describe('behavior', () => {
|
||||
describe('snapshot', () => {
|
||||
describe('disabled', () => {
|
||||
beforeEach(() => {
|
||||
useActionDisabledState.mockReturnValueOnce({ disableResumeCourse: true });
|
||||
wrapper = shallow(<ResumeButton {...props} />);
|
||||
});
|
||||
test('snapshot', () => {
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
it('should be disabled', () => {
|
||||
render(<IntlProvider locale="en"><ResumeButton {...props} /></IntlProvider>);
|
||||
const button = screen.getByRole('button', { name: 'Resume' });
|
||||
expect(button).toHaveClass('disabled');
|
||||
expect(button).toHaveAttribute('aria-disabled', 'true');
|
||||
expect(wrapper.instance.props.disabled).toEqual(true);
|
||||
});
|
||||
});
|
||||
describe('enabled', () => {
|
||||
it('should be enabled', () => {
|
||||
render(<IntlProvider locale="en"><ResumeButton {...props} /></IntlProvider>);
|
||||
const button = screen.getByRole('button', { name: 'Resume' });
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).not.toHaveClass('disabled');
|
||||
expect(button).not.toHaveAttribute('aria-disabled', 'true');
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<ResumeButton {...props} />);
|
||||
});
|
||||
it('should track enter course clicked event on click, with exec ed param', async () => {
|
||||
render(<IntlProvider locale="en"><ResumeButton {...props} /></IntlProvider>);
|
||||
const user = userEvent.setup();
|
||||
const button = screen.getByRole('button', { name: 'Resume' });
|
||||
user.click(button);
|
||||
expect(reduxHooks.useTrackCourseEvent).toHaveBeenCalledWith(
|
||||
test('snapshot', () => {
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
it('should be enabled', () => {
|
||||
expect(wrapper.instance.props.disabled).toEqual(false);
|
||||
});
|
||||
it('should track enter course clicked event on click, with exec ed param', () => {
|
||||
expect(wrapper.instance.props.onClick).toEqual(reduxHooks.useTrackCourseEvent(
|
||||
track.course.enterCourseClicked,
|
||||
props.cardId,
|
||||
resumeUrl + execEdPath(props.cardId),
|
||||
);
|
||||
));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import useActionDisabledState from '../hooks';
|
||||
@@ -9,35 +7,28 @@ import SelectSessionButton from './SelectSessionButton';
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useUpdateSelectSessionModalCallback: jest.fn(),
|
||||
useUpdateSelectSessionModalCallback: () => jest.fn().mockName('mockOpenSessionModal'),
|
||||
},
|
||||
}));
|
||||
jest.mock('../hooks', () => jest.fn(() => ({ disableSelectSession: false })));
|
||||
jest.mock('./ActionButton', () => 'ActionButton');
|
||||
|
||||
jest.mock('./ActionButton/hooks', () => jest.fn(() => false));
|
||||
let wrapper;
|
||||
|
||||
describe('SelectSessionButton', () => {
|
||||
const props = { cardId: 'cardId' };
|
||||
it('default render', () => {
|
||||
render(<IntlProvider locale="en"><SelectSessionButton {...props} /></IntlProvider>);
|
||||
const button = screen.getByRole('button', { name: 'Select Session' });
|
||||
expect(button).toBeInTheDocument();
|
||||
wrapper = shallow(<SelectSessionButton {...props} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
expect(wrapper.instance.props.disabled).toEqual(false);
|
||||
expect(wrapper.instance.props.onClick.getMockName()).toEqual(
|
||||
reduxHooks.useUpdateSelectSessionModalCallback().getMockName(),
|
||||
);
|
||||
});
|
||||
describe('if useActionDisabledState is false', () => {
|
||||
it('should disabled Select Session', () => {
|
||||
useActionDisabledState.mockReturnValueOnce({ disableSelectSession: true });
|
||||
render(<IntlProvider locale="en"><SelectSessionButton {...props} /></IntlProvider>);
|
||||
const button = screen.getByRole('button', { name: 'Select Session' });
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
});
|
||||
describe('on click', () => {
|
||||
it('should call openSessionModal', async () => {
|
||||
render(<IntlProvider locale="en"><SelectSessionButton {...props} /></IntlProvider>);
|
||||
const user = userEvent.setup();
|
||||
const button = screen.getByRole('button', { name: 'Select Session' });
|
||||
await user.click(button);
|
||||
expect(reduxHooks.useUpdateSelectSessionModalCallback).toHaveBeenCalledWith(props.cardId);
|
||||
});
|
||||
test('disabled states', () => {
|
||||
useActionDisabledState.mockReturnValueOnce({ disableSelectSession: true });
|
||||
wrapper = shallow(<SelectSessionButton {...props} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
expect(wrapper.instance.props.disabled).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import track from 'tracking';
|
||||
import { reduxHooks } from 'hooks';
|
||||
@@ -16,40 +14,32 @@ jest.mock('tracking', () => ({
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCourseRunData: jest.fn(() => ({ homeUrl: 'homeUrl' })),
|
||||
useTrackCourseEvent: jest.fn(),
|
||||
useTrackCourseEvent: jest.fn(
|
||||
(eventName, cardId, url) => ({ trackCourseEvent: { eventName, cardId, url } }),
|
||||
),
|
||||
},
|
||||
}));
|
||||
jest.mock('../hooks', () => jest.fn(() => ({ disableViewCourse: false })));
|
||||
|
||||
jest.mock('./ActionButton/hooks', () => jest.fn(() => false));
|
||||
jest.mock('./ActionButton', () => 'ActionButton');
|
||||
|
||||
const defaultProps = { cardId: 'cardId' };
|
||||
const homeUrl = 'homeUrl';
|
||||
|
||||
describe('ViewCourseButton', () => {
|
||||
it('learner can view course', async () => {
|
||||
render(<IntlProvider locale="en"><ViewCourseButton {...defaultProps} /></IntlProvider>);
|
||||
const button = screen.getByRole('button', { name: 'View Course' });
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).not.toHaveClass('disabled');
|
||||
expect(button).not.toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
it('calls trackCourseEvent on click', async () => {
|
||||
render(<IntlProvider locale="en"><ViewCourseButton {...defaultProps} /></IntlProvider>);
|
||||
const user = userEvent.setup();
|
||||
const button = screen.getByRole('button', { name: 'View Course' });
|
||||
await user.click(button);
|
||||
expect(reduxHooks.useTrackCourseEvent).toHaveBeenCalledWith(
|
||||
test('learner can view course', () => {
|
||||
const wrapper = shallow(<ViewCourseButton {...defaultProps} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
expect(wrapper.instance.props.onClick).toEqual(reduxHooks.useTrackCourseEvent(
|
||||
track.course.enterCourseClicked,
|
||||
defaultProps.cardId,
|
||||
homeUrl,
|
||||
);
|
||||
));
|
||||
expect(wrapper.instance.props.disabled).toEqual(false);
|
||||
});
|
||||
it('learner cannot view course', () => {
|
||||
test('learner cannot view course', () => {
|
||||
useActionDisabledState.mockReturnValueOnce({ disableViewCourse: true });
|
||||
render(<IntlProvider locale="en"><ViewCourseButton {...defaultProps} /></IntlProvider>);
|
||||
const button = screen.getByRole('button', { name: 'View Course' });
|
||||
expect(button).toHaveClass('disabled');
|
||||
expect(button).toHaveAttribute('aria-disabled', 'true');
|
||||
const wrapper = shallow(<ViewCourseButton {...defaultProps} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
expect(wrapper.instance.props.disabled).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`BeginCourseButton snapshot disabled snapshot 1`] = `
|
||||
<ActionButton
|
||||
as="a"
|
||||
disabled={true}
|
||||
href="#"
|
||||
onClick={
|
||||
{
|
||||
"trackCourseEvent": {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.enterCourseClicked],
|
||||
"url": "home-urlexec-ed-tracking-path=cardId",
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
Begin Course
|
||||
</ActionButton>
|
||||
`;
|
||||
|
||||
exports[`BeginCourseButton snapshot enabled snapshot 1`] = `
|
||||
<ActionButton
|
||||
as="a"
|
||||
disabled={false}
|
||||
href="#"
|
||||
onClick={
|
||||
{
|
||||
"trackCourseEvent": {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.enterCourseClicked],
|
||||
"url": "home-urlexec-ed-tracking-path=cardId",
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
Begin Course
|
||||
</ActionButton>
|
||||
`;
|
||||
@@ -0,0 +1,39 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ResumeButton snapshot disabled snapshot 1`] = `
|
||||
<ActionButton
|
||||
as="a"
|
||||
disabled={true}
|
||||
href="#"
|
||||
onClick={
|
||||
{
|
||||
"trackCourseEvent": {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.enterCourseClicked],
|
||||
"url": "resume-urlexec-ed-tracking-path=cardId",
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
Resume
|
||||
</ActionButton>
|
||||
`;
|
||||
|
||||
exports[`ResumeButton snapshot enabled snapshot 1`] = `
|
||||
<ActionButton
|
||||
as="a"
|
||||
disabled={false}
|
||||
href="#"
|
||||
onClick={
|
||||
{
|
||||
"trackCourseEvent": {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.enterCourseClicked],
|
||||
"url": "resume-urlexec-ed-tracking-path=cardId",
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
Resume
|
||||
</ActionButton>
|
||||
`;
|
||||
@@ -0,0 +1,19 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SelectSessionButton default render 1`] = `
|
||||
<ActionButton
|
||||
disabled={false}
|
||||
onClick={[MockFunction mockOpenSessionModal]}
|
||||
>
|
||||
Select Session
|
||||
</ActionButton>
|
||||
`;
|
||||
|
||||
exports[`SelectSessionButton disabled states 1`] = `
|
||||
<ActionButton
|
||||
disabled={true}
|
||||
onClick={[MockFunction mockOpenSessionModal]}
|
||||
>
|
||||
Select Session
|
||||
</ActionButton>
|
||||
`;
|
||||
@@ -0,0 +1,39 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ViewCourseButton learner can view course 1`] = `
|
||||
<ActionButton
|
||||
as="a"
|
||||
disabled={false}
|
||||
href="#"
|
||||
onClick={
|
||||
{
|
||||
"trackCourseEvent": {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.enterCourseClicked],
|
||||
"url": "homeUrl",
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
View Course
|
||||
</ActionButton>
|
||||
`;
|
||||
|
||||
exports[`ViewCourseButton learner cannot view course 1`] = `
|
||||
<ActionButton
|
||||
as="a"
|
||||
disabled={true}
|
||||
href="#"
|
||||
onClick={
|
||||
{
|
||||
"trackCourseEvent": {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.enterCourseClicked],
|
||||
"url": "homeUrl",
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
View Course
|
||||
</ActionButton>
|
||||
`;
|
||||
@@ -1,6 +1,13 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import CourseCardActionSlot from 'plugin-slots/CourseCardActionSlot';
|
||||
import SelectSessionButton from './SelectSessionButton';
|
||||
import BeginCourseButton from './BeginCourseButton';
|
||||
import ResumeButton from './ResumeButton';
|
||||
import ViewCourseButton from './ViewCourseButton';
|
||||
|
||||
import CourseCardActions from '.';
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
@@ -12,15 +19,16 @@ jest.mock('hooks', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('plugin-slots/CourseCardActionSlot', () => jest.fn(() => <div>CourseCardActionSlot</div>));
|
||||
jest.mock('./SelectSessionButton', () => jest.fn(() => <div>SelectSessionButton</div>));
|
||||
jest.mock('./ViewCourseButton', () => jest.fn(() => <div>ViewCourseButton</div>));
|
||||
jest.mock('./BeginCourseButton', () => jest.fn(() => <div>BeginCourseButton</div>));
|
||||
jest.mock('./ResumeButton', () => jest.fn(() => <div>ResumeButton</div>));
|
||||
jest.mock('plugin-slots/CourseCardActionSlot', () => 'CustomActionButton');
|
||||
jest.mock('./SelectSessionButton', () => 'SelectSessionButton');
|
||||
jest.mock('./ViewCourseButton', () => 'ViewCourseButton');
|
||||
jest.mock('./BeginCourseButton', () => 'BeginCourseButton');
|
||||
jest.mock('./ResumeButton', () => 'ResumeButton');
|
||||
|
||||
const cardId = 'test-card-id';
|
||||
const props = { cardId };
|
||||
|
||||
let el;
|
||||
describe('CourseCardActions', () => {
|
||||
const mockHooks = ({
|
||||
isEntitlement = false,
|
||||
@@ -36,11 +44,13 @@ describe('CourseCardActions', () => {
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ isExecEd2UCourse, isVerified, hasStarted });
|
||||
reduxHooks.useMasqueradeData.mockReturnValueOnce({ isMasquerading });
|
||||
};
|
||||
const renderComponent = () => render(<CourseCardActions {...props} />);
|
||||
describe('hooks', () => {
|
||||
const render = () => {
|
||||
el = shallow(<CourseCardActions {...props} />);
|
||||
};
|
||||
describe('behavior', () => {
|
||||
it('initializes redux hooks', () => {
|
||||
mockHooks();
|
||||
renderComponent();
|
||||
render();
|
||||
expect(reduxHooks.useCardEntitlementData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useCardEnrollmentData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(cardId);
|
||||
@@ -50,44 +60,36 @@ describe('CourseCardActions', () => {
|
||||
describe('entitlement course', () => {
|
||||
it('renders ViewCourseButton if fulfilled', () => {
|
||||
mockHooks({ isEntitlement: true, isFulfilled: true });
|
||||
renderComponent();
|
||||
const ViewCourseButton = screen.getByText('ViewCourseButton');
|
||||
expect(ViewCourseButton).toBeInTheDocument();
|
||||
render();
|
||||
expect(el.instance.findByType(ViewCourseButton)[0].props.cardId).toEqual(cardId);
|
||||
});
|
||||
it('renders SelectSessionButton if not fulfilled', () => {
|
||||
mockHooks({ isEntitlement: true });
|
||||
renderComponent();
|
||||
const SelectSessionButton = screen.getByText('SelectSessionButton');
|
||||
expect(SelectSessionButton).toBeInTheDocument();
|
||||
render();
|
||||
expect(el.instance.findByType(SelectSessionButton)[0].props.cardId).toEqual(cardId);
|
||||
});
|
||||
});
|
||||
describe('not entitlement, verified, or exec ed', () => {
|
||||
it('renders CourseCardActionSlot and ViewCourseButton for archived courses', () => {
|
||||
mockHooks({ isArchived: true });
|
||||
renderComponent();
|
||||
const CourseCardActionSlot = screen.getByText('CourseCardActionSlot');
|
||||
expect(CourseCardActionSlot).toBeInTheDocument();
|
||||
const ViewCourseButton = screen.getByText('ViewCourseButton');
|
||||
expect(ViewCourseButton).toBeInTheDocument();
|
||||
render();
|
||||
expect(el.instance.findByType(CourseCardActionSlot)[0].props.cardId).toEqual(cardId);
|
||||
expect(el.instance.findByType(ViewCourseButton)[0].props.cardId).toEqual(cardId);
|
||||
});
|
||||
describe('unstarted courses', () => {
|
||||
it('renders CourseCardActionSlot and BeginCourseButton', () => {
|
||||
mockHooks();
|
||||
renderComponent();
|
||||
const CourseCardActionSlot = screen.getByText('CourseCardActionSlot');
|
||||
expect(CourseCardActionSlot).toBeInTheDocument();
|
||||
const BeginCourseButton = screen.getByText('BeginCourseButton');
|
||||
expect(BeginCourseButton).toBeInTheDocument();
|
||||
render();
|
||||
expect(el.instance.findByType(CourseCardActionSlot)[0].props.cardId).toEqual(cardId);
|
||||
expect(el.instance.findByType(BeginCourseButton)[0].props.cardId).toEqual(cardId);
|
||||
});
|
||||
});
|
||||
describe('active courses (started, and not archived)', () => {
|
||||
it('renders CourseCardActionSlot and ResumeButton', () => {
|
||||
mockHooks({ hasStarted: true });
|
||||
renderComponent();
|
||||
const CourseCardActionSlot = screen.getByText('CourseCardActionSlot');
|
||||
expect(CourseCardActionSlot).toBeInTheDocument();
|
||||
const ResumeButton = screen.getByText('ResumeButton');
|
||||
expect(ResumeButton).toBeInTheDocument();
|
||||
render();
|
||||
expect(el.instance.findByType(CourseCardActionSlot)[0].props.cardId).toEqual(cardId);
|
||||
expect(el.instance.findByType(ResumeButton)[0].props.cardId).toEqual(cardId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import CertificateBanner from './CertificateBanner';
|
||||
import messages from './messages';
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
utilHooks: {
|
||||
@@ -17,28 +17,28 @@ jest.mock('hooks', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const defaultCertificate = {
|
||||
availableDate: '10/20/3030',
|
||||
isRestricted: false,
|
||||
isDownloadable: false,
|
||||
isEarnedButUnavailable: false,
|
||||
};
|
||||
const defaultEnrollment = {
|
||||
isAudit: false,
|
||||
isVerified: false,
|
||||
};
|
||||
const defaultCourseRun = { isArchived: false };
|
||||
const defaultGrade = { isPassing: false };
|
||||
const defaultPlatformSettings = {};
|
||||
const props = { cardId: 'cardId' };
|
||||
const supportEmail = 'suport@email.com';
|
||||
const billingEmail = 'billing@email.com';
|
||||
jest.mock('components/Banner', () => 'Banner');
|
||||
|
||||
describe('CertificateBanner', () => {
|
||||
const props = { cardId: 'cardId' };
|
||||
reduxHooks.useCardCourseRunData.mockReturnValue({
|
||||
minPassingGrade: 0.8,
|
||||
progressUrl: 'progressUrl',
|
||||
});
|
||||
|
||||
const defaultCertificate = {
|
||||
availableDate: '10/20/3030',
|
||||
isRestricted: false,
|
||||
isDownloadable: false,
|
||||
isEarnedButUnavailable: false,
|
||||
};
|
||||
const defaultEnrollment = {
|
||||
isAudit: false,
|
||||
isVerified: false,
|
||||
};
|
||||
const defaultCourseRun = { isArchived: false };
|
||||
const defaultGrade = { isPassing: false };
|
||||
const defaultPlatformSettings = {};
|
||||
const createWrapper = ({
|
||||
certificate = {},
|
||||
enrollment = {},
|
||||
@@ -51,192 +51,177 @@ describe('CertificateBanner', () => {
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ ...defaultEnrollment, ...enrollment });
|
||||
reduxHooks.useCardCourseRunData.mockReturnValueOnce({ ...defaultCourseRun, ...courseRun });
|
||||
reduxHooks.usePlatformSettingsData.mockReturnValueOnce({ ...defaultPlatformSettings, ...platformSettings });
|
||||
return render(<IntlProvider locale="en"><CertificateBanner {...props} /></IntlProvider>);
|
||||
return shallow(<CertificateBanner {...props} />);
|
||||
};
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('is restricted', () => {
|
||||
createWrapper({
|
||||
certificate: {
|
||||
isRestricted: true,
|
||||
},
|
||||
/** TODO: Update tests to validate snapshots **/
|
||||
describe('snapshot', () => {
|
||||
test('is restricted', () => {
|
||||
const wrapper = createWrapper({
|
||||
certificate: {
|
||||
isRestricted: true,
|
||||
},
|
||||
});
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
const banner = screen.getByRole('alert');
|
||||
expect(banner).toBeInTheDocument();
|
||||
const msg = screen.getByText((text) => text.includes('please let us know.'));
|
||||
expect(msg).toBeInTheDocument();
|
||||
expect(msg).not.toContain(supportEmail);
|
||||
});
|
||||
it('is restricted with support email', () => {
|
||||
createWrapper({
|
||||
certificate: {
|
||||
isRestricted: true,
|
||||
},
|
||||
platformSettings: {
|
||||
supportEmail,
|
||||
},
|
||||
test('is restricted with support email', () => {
|
||||
const wrapper = createWrapper({
|
||||
certificate: {
|
||||
isRestricted: true,
|
||||
},
|
||||
platformSettings: {
|
||||
supportEmail: 'suport@email',
|
||||
},
|
||||
});
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
const banner = screen.getByRole('alert');
|
||||
expect(banner).toBeInTheDocument();
|
||||
const msg = screen.getByText((text) => text.includes(supportEmail));
|
||||
expect(msg).toBeInTheDocument();
|
||||
});
|
||||
it('is restricted with billing email but not verified', () => {
|
||||
createWrapper({
|
||||
certificate: {
|
||||
isRestricted: true,
|
||||
},
|
||||
platformSettings: {
|
||||
billingEmail,
|
||||
},
|
||||
test('is restricted with billing email', () => {
|
||||
const wrapper = createWrapper({
|
||||
certificate: {
|
||||
isRestricted: true,
|
||||
},
|
||||
platformSettings: {
|
||||
billingEmail: 'billing@email',
|
||||
},
|
||||
});
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
const banner = screen.getByRole('alert');
|
||||
expect(banner).toBeInTheDocument();
|
||||
expect(banner).toHaveClass('alert-danger');
|
||||
const msg = screen.queryByText((text) => text.includes(billingEmail));
|
||||
expect(msg).not.toBeInTheDocument();
|
||||
});
|
||||
it('is restricted and verified', () => {
|
||||
createWrapper({
|
||||
certificate: {
|
||||
isRestricted: true,
|
||||
},
|
||||
enrollment: {
|
||||
isVerified: true,
|
||||
},
|
||||
test('is restricted and verified', () => {
|
||||
const wrapper = createWrapper({
|
||||
certificate: {
|
||||
isRestricted: true,
|
||||
},
|
||||
enrollment: {
|
||||
isVerified: true,
|
||||
},
|
||||
});
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
const banner = screen.getByRole('alert');
|
||||
expect(banner).toBeInTheDocument();
|
||||
const restrictedMsg = screen.getByText((text) => text.includes('please let us know.'));
|
||||
expect(restrictedMsg).toBeInTheDocument();
|
||||
const refundMsg = screen.getByText((text) => text.includes('If you would like a refund'));
|
||||
expect(refundMsg).toBeInTheDocument();
|
||||
});
|
||||
it('is restricted and verified with support email', () => {
|
||||
createWrapper({
|
||||
certificate: {
|
||||
isRestricted: true,
|
||||
},
|
||||
enrollment: {
|
||||
isVerified: true,
|
||||
},
|
||||
platformSettings: {
|
||||
supportEmail,
|
||||
},
|
||||
test('is restricted and verified with support email', () => {
|
||||
const wrapper = createWrapper({
|
||||
certificate: {
|
||||
isRestricted: true,
|
||||
},
|
||||
enrollment: {
|
||||
isVerified: true,
|
||||
},
|
||||
platformSettings: {
|
||||
supportEmail: 'suport@email',
|
||||
},
|
||||
});
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
const restrictedMsg = screen.getByText((text) => text.includes(supportEmail));
|
||||
expect(restrictedMsg).toBeInTheDocument();
|
||||
const refundMsg = screen.getByText((text) => text.includes('If you would like a refund'));
|
||||
expect(refundMsg).toBeInTheDocument();
|
||||
expect(refundMsg.innerHTML).not.toContain(billingEmail);
|
||||
});
|
||||
it('is restricted and verified with billing email', () => {
|
||||
createWrapper({
|
||||
certificate: {
|
||||
isRestricted: true,
|
||||
},
|
||||
enrollment: {
|
||||
isVerified: true,
|
||||
},
|
||||
platformSettings: {
|
||||
billingEmail,
|
||||
},
|
||||
test('is restricted and verified with billing email', () => {
|
||||
const wrapper = createWrapper({
|
||||
certificate: {
|
||||
isRestricted: true,
|
||||
},
|
||||
enrollment: {
|
||||
isVerified: true,
|
||||
},
|
||||
platformSettings: {
|
||||
billingEmail: 'billing@email',
|
||||
},
|
||||
});
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
const restrictedMsg = screen.queryByText((text) => text.includes('please let us know.'));
|
||||
expect(restrictedMsg).toBeInTheDocument();
|
||||
expect(restrictedMsg.innerHTML).not.toContain(supportEmail);
|
||||
const refundMsg = screen.getByText((text) => text.includes(billingEmail));
|
||||
expect(refundMsg).toBeInTheDocument();
|
||||
});
|
||||
it('is restricted and verified with support and billing email', () => {
|
||||
createWrapper({
|
||||
certificate: {
|
||||
isRestricted: true,
|
||||
},
|
||||
enrollment: {
|
||||
isVerified: true,
|
||||
},
|
||||
platformSettings: {
|
||||
supportEmail,
|
||||
billingEmail,
|
||||
},
|
||||
test('is restricted and verified with support and billing email', () => {
|
||||
const wrapper = createWrapper({
|
||||
certificate: {
|
||||
isRestricted: true,
|
||||
},
|
||||
enrollment: {
|
||||
isVerified: true,
|
||||
},
|
||||
platformSettings: {
|
||||
supportEmail: 'suport@email',
|
||||
billingEmail: 'billing@email',
|
||||
},
|
||||
});
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
const restrictedMsg = screen.getByText((text) => text.includes(supportEmail));
|
||||
expect(restrictedMsg).toBeInTheDocument();
|
||||
const refundMsg = screen.getByText((text) => text.includes(billingEmail));
|
||||
expect(refundMsg).toBeInTheDocument();
|
||||
});
|
||||
it('is passing and is downloadable', () => {
|
||||
createWrapper({
|
||||
grade: { isPassing: true },
|
||||
certificate: { isDownloadable: true },
|
||||
test('is passing and is downloadable', () => {
|
||||
const wrapper = createWrapper({
|
||||
grade: { isPassing: true },
|
||||
certificate: { isDownloadable: true },
|
||||
});
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
const banner = screen.getByRole('alert');
|
||||
expect(banner).toBeInTheDocument();
|
||||
expect(banner).toHaveClass('alert-success');
|
||||
const readyMsg = screen.getByText((text) => text.includes('Congratulations.'));
|
||||
expect(readyMsg).toBeInTheDocument();
|
||||
});
|
||||
it('not passing and is downloadable', () => {
|
||||
createWrapper({
|
||||
grade: { isPassing: false },
|
||||
certificate: { isDownloadable: true },
|
||||
test('not passing and is downloadable', () => {
|
||||
const wrapper = createWrapper({
|
||||
grade: { isPassing: false },
|
||||
certificate: { isDownloadable: true },
|
||||
});
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
const banner = screen.getByRole('alert');
|
||||
expect(banner).toBeInTheDocument();
|
||||
expect(banner).toHaveClass('alert-success');
|
||||
const readyMsg = screen.getByText((text) => text.includes('Congratulations.'));
|
||||
expect(readyMsg).toBeInTheDocument();
|
||||
});
|
||||
it('not passing and audit', () => {
|
||||
createWrapper({
|
||||
enrollment: {
|
||||
isAudit: true,
|
||||
},
|
||||
test('not passing and audit', () => {
|
||||
const wrapper = createWrapper({
|
||||
enrollment: {
|
||||
isAudit: true,
|
||||
},
|
||||
});
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
const banner = screen.getByRole('alert');
|
||||
expect(banner).toHaveClass('alert-info');
|
||||
const auditMsg = screen.getByText((text) => text.includes('Grade required to pass the course:'));
|
||||
expect(auditMsg).toBeInTheDocument();
|
||||
});
|
||||
it('not passing and has finished', () => {
|
||||
createWrapper({
|
||||
courseRun: { isArchived: true },
|
||||
test('not passing and has finished', () => {
|
||||
const wrapper = createWrapper({
|
||||
courseRun: { isArchived: true },
|
||||
});
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
const banner = screen.getByRole('alert');
|
||||
expect(banner).toHaveClass('alert-warning');
|
||||
const archivedMsg = screen.getByText('You are not eligible for a certificate.');
|
||||
expect(archivedMsg).toBeInTheDocument();
|
||||
});
|
||||
it('not passing and not audit and not finished', () => {
|
||||
createWrapper({});
|
||||
const banner = screen.getByRole('alert');
|
||||
expect(banner).toHaveClass('alert-warning');
|
||||
const msg = screen.getByText((text) => text.includes('Grade required for a certificate'));
|
||||
expect(msg).toBeInTheDocument();
|
||||
});
|
||||
it('is passing and is earned but unavailable', () => {
|
||||
createWrapper({
|
||||
grade: {
|
||||
isPassing: true,
|
||||
},
|
||||
certificate: {
|
||||
isEarnedButUnavailable: true,
|
||||
},
|
||||
test('not passing and not audit and not finished', () => {
|
||||
const wrapper = createWrapper({});
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
const banner = screen.getByRole('alert');
|
||||
expect(banner).toHaveClass('alert-info');
|
||||
const earnedMsg = screen.getByText((text) => text.includes('Your grade and certificate will be ready after'));
|
||||
expect(earnedMsg).toBeInTheDocument();
|
||||
});
|
||||
it('is passing and not downloadable render empty', () => {
|
||||
createWrapper({
|
||||
grade: {
|
||||
isPassing: true,
|
||||
},
|
||||
test('is passing and is earned but unavailable', () => {
|
||||
const wrapper = createWrapper({
|
||||
grade: {
|
||||
isPassing: true,
|
||||
},
|
||||
certificate: {
|
||||
isEarnedButUnavailable: true,
|
||||
},
|
||||
});
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
test('is passing and not downloadable render empty', () => {
|
||||
const wrapper = createWrapper({
|
||||
grade: {
|
||||
isPassing: true,
|
||||
},
|
||||
});
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
describe('behavior', () => {
|
||||
it('is restricted', () => {
|
||||
const wrapper = createWrapper({
|
||||
certificate: {
|
||||
isRestricted: true,
|
||||
},
|
||||
platformSettings: {
|
||||
supportEmail: 'suport@email',
|
||||
billingEmail: 'billing@email',
|
||||
},
|
||||
});
|
||||
const bannerMessage = wrapper.instance.findByType('format-message-function').map(el => el.props.message.defaultMessage).join('\n');
|
||||
expect(bannerMessage).toEqual(messages.certRestricted.defaultMessage);
|
||||
expect(bannerMessage).toContain(messages.certRestricted.defaultMessage);
|
||||
});
|
||||
it('is restricted and verified', () => {
|
||||
const wrapper = createWrapper({
|
||||
certificate: {
|
||||
isRestricted: true,
|
||||
},
|
||||
enrollment: {
|
||||
isVerified: true,
|
||||
},
|
||||
platformSettings: {
|
||||
supportEmail: 'suport@email',
|
||||
billingEmail: 'billing@email',
|
||||
},
|
||||
});
|
||||
const bannerMessage = wrapper.instance.findByType('format-message-function').map(el => el.props.message.defaultMessage).join('\n');
|
||||
expect(bannerMessage).toContain(messages.certRestricted.defaultMessage);
|
||||
expect(bannerMessage).toContain(messages.certRefundContactBilling.defaultMessage);
|
||||
});
|
||||
const banner = screen.queryByRole('alert');
|
||||
expect(banner).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
import { Hyperlink } from '@openedx/paragon';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { formatMessage } from 'testUtils';
|
||||
@@ -7,6 +8,7 @@ import { CourseBanner } from './CourseBanner';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
jest.mock('components/Banner', () => 'Banner');
|
||||
jest.mock('hooks', () => ({
|
||||
utilHooks: {
|
||||
useFormatDate: () => date => date,
|
||||
@@ -17,7 +19,9 @@ jest.mock('hooks', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const cardId = 'test-card-id';
|
||||
const cardId = 'my-test-course-number';
|
||||
|
||||
let el;
|
||||
|
||||
const enrollmentData = {
|
||||
isVerified: false,
|
||||
@@ -34,7 +38,7 @@ const courseRunData = {
|
||||
marketingUrl: 'marketing-url',
|
||||
};
|
||||
|
||||
const renderCourseBanner = (overrides = {}) => {
|
||||
const render = (overrides = {}) => {
|
||||
const {
|
||||
courseRun = {},
|
||||
enrollment = {},
|
||||
@@ -47,58 +51,81 @@ const renderCourseBanner = (overrides = {}) => {
|
||||
...enrollmentData,
|
||||
...enrollment,
|
||||
});
|
||||
return render(<IntlProvider locale="en"><CourseBanner cardId={cardId} /></IntlProvider>);
|
||||
el = shallow(<CourseBanner cardId={cardId} />);
|
||||
};
|
||||
|
||||
describe('CourseBanner', () => {
|
||||
it('initializes data with course number from enrollment, course and course run data', () => {
|
||||
renderCourseBanner();
|
||||
test('initializes data with course number from enrollment, course and course run data', () => {
|
||||
render();
|
||||
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useCardEnrollmentData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
it('no display if learner is verified', () => {
|
||||
renderCourseBanner({ enrollment: { isVerified: true } });
|
||||
expect(screen.queryByRole('alert')).toBeNull();
|
||||
test('no display if learner is verified', () => {
|
||||
render({ enrollment: { isVerified: true } });
|
||||
expect(el.isEmptyRender()).toEqual(true);
|
||||
});
|
||||
describe('audit access expired', () => {
|
||||
it('should display correct message and link', () => {
|
||||
renderCourseBanner({ enrollment: { isAuditAccessExpired: true } });
|
||||
const auditAccessText = screen.getByText(formatMessage(messages.auditAccessExpired));
|
||||
expect(auditAccessText).toBeInTheDocument();
|
||||
const auditAccessLink = screen.getByText(formatMessage(messages.findAnotherCourse));
|
||||
expect(auditAccessLink).toBeInTheDocument();
|
||||
beforeEach(() => {
|
||||
render({ enrollment: { isAuditAccessExpired: true } });
|
||||
});
|
||||
test('snapshot: (auditAccessExpired, findAnotherCourse hyperlink)', () => {
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
test('messages: auditAccessExpired', () => {
|
||||
expect(el.instance.children[0].children[0].el).toContain(messages.auditAccessExpired.defaultMessage);
|
||||
expect(el.instance.findByType(Hyperlink)[0].children[0].el).toEqual(messages.findAnotherCourse.defaultMessage);
|
||||
});
|
||||
});
|
||||
describe('unmet prerequisites', () => {
|
||||
it('should display correct message', () => {
|
||||
renderCourseBanner({ enrollment: { coursewareAccess: { hasUnmetPrerequisites: true } } });
|
||||
const preReqText = screen.getByText(formatMessage(messages.prerequisitesNotMet));
|
||||
expect(preReqText).toBeInTheDocument();
|
||||
beforeEach(() => {
|
||||
render({ enrollment: { coursewareAccess: { hasUnmetPrerequisites: true } } });
|
||||
});
|
||||
test('snapshot: unmetPrerequisites', () => {
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
test('messages: prerequisitesNotMet', () => {
|
||||
expect(el.instance.children[0].children[0].el).toContain(messages.prerequisitesNotMet.defaultMessage);
|
||||
});
|
||||
});
|
||||
describe('too early', () => {
|
||||
describe('no start date', () => {
|
||||
it('should not display banner', () => {
|
||||
renderCourseBanner({ enrollment: { coursewareAccess: { isTooEarly: true } }, courseRun: { startDate: null } });
|
||||
const banner = screen.queryByRole('alert');
|
||||
expect(banner).toBeNull();
|
||||
beforeEach(() => {
|
||||
render({ enrollment: { coursewareAccess: { isTooEarly: true } }, courseRun: { startDate: null } });
|
||||
});
|
||||
test('snapshot', () => expect(el.snapshot).toMatchSnapshot());
|
||||
test('messages', () => expect(el.instance.children).toEqual([]));
|
||||
});
|
||||
describe('has start date', () => {
|
||||
it('should display messages courseHasNotStarted', () => {
|
||||
renderCourseBanner({ enrollment: { coursewareAccess: { isTooEarly: true } } });
|
||||
const earlyMsg = screen.getByText(
|
||||
beforeEach(() => {
|
||||
render({ enrollment: { coursewareAccess: { isTooEarly: true } } });
|
||||
});
|
||||
test('snapshot', () => expect(el.snapshot).toMatchSnapshot());
|
||||
|
||||
test('messages: courseHasNotStarted', () => {
|
||||
expect(el.instance.children[0].children[0].el).toContain(
|
||||
formatMessage(messages.courseHasNotStarted, { startDate: courseRunData.startDate }),
|
||||
);
|
||||
expect(earlyMsg).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('staff', () => {
|
||||
it('should not display banner', () => {
|
||||
renderCourseBanner({ enrollment: { coursewareAccess: { isStaff: true } } });
|
||||
const banner = screen.queryByRole('alert');
|
||||
expect(banner).toBeNull();
|
||||
beforeEach(() => {
|
||||
render({ enrollment: { coursewareAccess: { isStaff: true } } });
|
||||
});
|
||||
test('snapshot: isStaff', () => {
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
test('snapshot: stacking banners', () => {
|
||||
render({
|
||||
enrollment: {
|
||||
coursewareAccess: {
|
||||
isStaff: true,
|
||||
isTooEarly: true,
|
||||
hasUnmetPrerequisites: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CreditBanner component render with error state snapshot 1`] = `
|
||||
<Banner
|
||||
variant="danger"
|
||||
>
|
||||
<p
|
||||
className="credit-error-msg"
|
||||
data-testid="credit-error-msg"
|
||||
>
|
||||
<format-message-function
|
||||
message={
|
||||
{
|
||||
"defaultMessage": "An error occurred with this transaction. For help, contact {supportEmailLink}.",
|
||||
"description": "",
|
||||
"id": "learner-dash.courseCard.banners.credit.error",
|
||||
}
|
||||
}
|
||||
values={
|
||||
{
|
||||
"supportEmailLink": <MailtoLink
|
||||
to="test-support-email"
|
||||
>
|
||||
test-support-email
|
||||
</MailtoLink>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
<ContentComponent
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
</Banner>
|
||||
`;
|
||||
|
||||
exports[`CreditBanner component render with error state with no email snapshot 1`] = `
|
||||
<Banner
|
||||
variant="danger"
|
||||
>
|
||||
<p
|
||||
className="credit-error-msg"
|
||||
data-testid="credit-error-msg"
|
||||
>
|
||||
An error occurred with this transaction.
|
||||
</p>
|
||||
<ContentComponent
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
</Banner>
|
||||
`;
|
||||
|
||||
exports[`CreditBanner component render with no error state snapshot 1`] = `
|
||||
<Banner>
|
||||
<ContentComponent
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
</Banner>
|
||||
`;
|
||||
@@ -1,65 +1,95 @@
|
||||
import { screen, render } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { formatMessage } from 'testUtils';
|
||||
import { MailtoLink } from '@openedx/paragon';
|
||||
|
||||
import hooks from './hooks';
|
||||
import { CreditBanner } from '.';
|
||||
import messages from './messages';
|
||||
import CreditBanner from '.';
|
||||
|
||||
jest.mock('components/Banner', () => 'Banner');
|
||||
|
||||
jest.mock('./hooks', () => ({
|
||||
useCreditBannerData: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('CreditBanner', () => {
|
||||
const mockCardId = 'test-card-id';
|
||||
const mockContentComponent = () => <div data-testid="mock-content">Test Content</div>;
|
||||
const mockSupportEmail = 'support@test.com';
|
||||
let el;
|
||||
const cardId = 'test-card-id';
|
||||
|
||||
const renderCreditBanner = () => render(
|
||||
<IntlProvider locale="en">
|
||||
<CreditBanner cardId={mockCardId} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
const ContentComponent = () => 'ContentComponent';
|
||||
const supportEmail = 'test-support-email';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return null if hook returns null', () => {
|
||||
hooks.useCreditBannerData.mockReturnValue(null);
|
||||
renderCreditBanner();
|
||||
const banner = screen.queryByRole('alert');
|
||||
expect(banner).toBeNull();
|
||||
});
|
||||
|
||||
it('should render content component without error', () => {
|
||||
hooks.useCreditBannerData.mockReturnValue({
|
||||
ContentComponent: mockContentComponent,
|
||||
error: false,
|
||||
describe('CreditBanner component', () => {
|
||||
describe('behavior', () => {
|
||||
beforeEach(() => {
|
||||
hooks.useCreditBannerData.mockReturnValue(null);
|
||||
el = shallow(<CreditBanner cardId={cardId} />);
|
||||
});
|
||||
renderCreditBanner();
|
||||
|
||||
expect(screen.getByTestId('mock-content')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('credit-error-msg')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render error message with support email', () => {
|
||||
hooks.useCreditBannerData.mockReturnValue({
|
||||
ContentComponent: mockContentComponent,
|
||||
error: true,
|
||||
supportEmail: mockSupportEmail,
|
||||
it('initializes hooks with cardId', () => {
|
||||
expect(hooks.useCreditBannerData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
renderCreditBanner();
|
||||
|
||||
expect(screen.getByTestId('credit-error-msg')).toBeInTheDocument();
|
||||
expect(screen.getByText(mockSupportEmail)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render error message without support email', () => {
|
||||
hooks.useCreditBannerData.mockReturnValue({
|
||||
ContentComponent: mockContentComponent,
|
||||
error: true,
|
||||
it('returns null if hookData is null', () => {
|
||||
expect(el.isEmptyRender()).toEqual(true);
|
||||
});
|
||||
});
|
||||
describe('render', () => {
|
||||
describe('with error state', () => {
|
||||
beforeEach(() => {
|
||||
hooks.useCreditBannerData.mockReturnValue({
|
||||
error: true,
|
||||
ContentComponent,
|
||||
supportEmail,
|
||||
});
|
||||
el = shallow(<CreditBanner cardId={cardId} />);
|
||||
});
|
||||
test('snapshot', () => {
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
it('passes danger variant to Banner parent', () => {
|
||||
expect(el.instance.findByType('Banner')[0].props.variant).toEqual('danger');
|
||||
});
|
||||
it('includes credit-error-msg with support email link', () => {
|
||||
expect(el.instance.findByTestId('credit-error-msg')[0].children[0].el).toEqual(shallow(formatMessage(messages.error, {
|
||||
supportEmailLink: (<MailtoLink to={supportEmail}>{supportEmail}</MailtoLink>),
|
||||
})));
|
||||
});
|
||||
it('loads ContentComponent with cardId', () => {
|
||||
expect(el.instance.findByType('ContentComponent')[0].props.cardId).toEqual(cardId);
|
||||
});
|
||||
});
|
||||
renderCreditBanner();
|
||||
|
||||
expect(screen.getByTestId('credit-error-msg')).toBeInTheDocument();
|
||||
expect(screen.queryByText(mockSupportEmail)).not.toBeInTheDocument();
|
||||
describe('with error state with no email', () => {
|
||||
beforeEach(() => {
|
||||
hooks.useCreditBannerData.mockReturnValue({
|
||||
error: true,
|
||||
ContentComponent,
|
||||
});
|
||||
el = shallow(<CreditBanner cardId={cardId} />);
|
||||
});
|
||||
test('snapshot', () => {
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
it('includes credit-error-msg without support email link', () => {
|
||||
expect(el.instance.findByTestId('credit-error-msg')[0].children[0].el).toEqual(formatMessage(messages.errorNoEmail));
|
||||
});
|
||||
});
|
||||
|
||||
describe('with no error state', () => {
|
||||
beforeEach(() => {
|
||||
hooks.useCreditBannerData.mockReturnValue({
|
||||
error: false,
|
||||
ContentComponent,
|
||||
supportEmail,
|
||||
});
|
||||
el = shallow(<CreditBanner cardId={cardId} />);
|
||||
});
|
||||
test('snapshot', () => {
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
it('loads ContentComponent with cardId', () => {
|
||||
expect(el.instance.findByType('ContentComponent')[0].props.cardId).toEqual(cardId);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { formatMessage } from 'testUtils';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import messages from './messages';
|
||||
import ProviderLink from './components/ProviderLink';
|
||||
import ApprovedContent from './ApprovedContent';
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
@@ -11,7 +13,10 @@ jest.mock('hooks', () => ({
|
||||
useMasqueradeData: jest.fn(),
|
||||
},
|
||||
}));
|
||||
jest.mock('./components/CreditContent', () => 'CreditContent');
|
||||
jest.mock('./components/ProviderLink', () => 'ProviderLink');
|
||||
|
||||
let el;
|
||||
const cardId = 'test-card-id';
|
||||
const credit = {
|
||||
providerStatusUrl: 'test-credit-provider-status-url',
|
||||
@@ -21,54 +26,38 @@ reduxHooks.useCardCreditData.mockReturnValue(credit);
|
||||
reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: false });
|
||||
|
||||
describe('ApprovedContent component', () => {
|
||||
describe('hooks', () => {
|
||||
beforeEach(() => {
|
||||
el = shallow(<ApprovedContent cardId={cardId} />);
|
||||
});
|
||||
describe('behavior', () => {
|
||||
it('initializes credit data with cardId', () => {
|
||||
render(<IntlProvider locale="en"><ApprovedContent cardId={cardId} /></IntlProvider>);
|
||||
expect(reduxHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
});
|
||||
describe('render', () => {
|
||||
describe('rendered CreditContent component', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
render(<IntlProvider locale="en"><ApprovedContent cardId={cardId} /></IntlProvider>);
|
||||
let component;
|
||||
beforeAll(() => {
|
||||
component = el.instance.findByType('CreditContent');
|
||||
});
|
||||
it('action.message is formatted viewCredit message', () => {
|
||||
const actionButton = screen.getByRole('link', { name: messages.viewCredit.defaultMessage });
|
||||
expect(actionButton).toBeInTheDocument();
|
||||
expect(actionButton).toHaveTextContent(formatMessage(messages.viewCredit));
|
||||
test('action.href from credit.providerStatusUrl', () => {
|
||||
expect(component[0].props.action.href).toEqual(credit.providerStatusUrl);
|
||||
});
|
||||
it('action.href from credit.providerStatusUrl', () => {
|
||||
const actionButton = screen.getByRole('link', { name: messages.viewCredit.defaultMessage });
|
||||
expect(actionButton).toHaveAttribute('href', credit.providerStatusUrl);
|
||||
test('action.message is formatted viewCredit message', () => {
|
||||
expect(component[0].props.action.message).toEqual(formatMessage(messages.viewCredit));
|
||||
});
|
||||
it('action.disabled is false', () => {
|
||||
const actionButton = screen.getByRole('link', { name: messages.viewCredit.defaultMessage });
|
||||
expect(actionButton).not.toHaveAttribute('aria-disabled', 'true');
|
||||
expect(actionButton).not.toHaveClass('disabled');
|
||||
expect(actionButton).toBeEnabled();
|
||||
test('action.disabled is false', () => {
|
||||
expect(component[0].props.action.disabled).toEqual(false);
|
||||
});
|
||||
it('message is formatted approved message', () => {
|
||||
const creditMsg = screen.getByTestId('credit-msg');
|
||||
expect(creditMsg).toBeInTheDocument();
|
||||
expect(creditMsg.textContent).toContain(`${credit.providerName} has approved your request for course credit`);
|
||||
});
|
||||
});
|
||||
describe('when masquerading', () => {
|
||||
beforeEach(() => {
|
||||
reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: true });
|
||||
render(<IntlProvider locale="en"><ApprovedContent cardId={cardId} /></IntlProvider>);
|
||||
});
|
||||
|
||||
it('disables the action button', () => {
|
||||
const actionButton = screen.getByRole('link', { name: messages.viewCredit.defaultMessage });
|
||||
expect(actionButton).toHaveAttribute('aria-disabled', 'true');
|
||||
expect(actionButton).toHaveClass('disabled');
|
||||
});
|
||||
|
||||
it('still renders provider name and link correctly', () => {
|
||||
const creditMsg = screen.getByTestId('credit-msg');
|
||||
expect(creditMsg.textContent).toContain(credit.providerName);
|
||||
test('message is formatted approved message', () => {
|
||||
expect(component[0].props.message).toEqual(formatMessage(
|
||||
messages.approved,
|
||||
{
|
||||
congratulations: (<b>{formatMessage(messages.congratulations)}</b>),
|
||||
linkToProviderSite: <ProviderLink cardId={cardId} />,
|
||||
providerName: credit.providerName,
|
||||
},
|
||||
));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { formatMessage } from 'testUtils';
|
||||
import track from 'tracking';
|
||||
|
||||
import messages from './messages';
|
||||
@@ -14,13 +14,16 @@ jest.mock('hooks', () => ({
|
||||
useCardCourseRunData: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('./components/CreditContent', () => 'CreditContent');
|
||||
jest.mock('tracking', () => ({
|
||||
credit: {
|
||||
purchase: jest.fn(),
|
||||
purchase: (...args) => ({ trackCredit: args }),
|
||||
},
|
||||
}));
|
||||
|
||||
let el;
|
||||
let component;
|
||||
|
||||
const cardId = 'test-card-id';
|
||||
const courseId = 'test-course-id';
|
||||
const credit = {
|
||||
@@ -29,45 +32,50 @@ const credit = {
|
||||
reduxHooks.useCardCreditData.mockReturnValue(credit);
|
||||
reduxHooks.useCardCourseRunData.mockReturnValue({ courseId });
|
||||
|
||||
const renderEligibleContent = () => render(<IntlProvider locale="en" messages={{}}><EligibleContent cardId={cardId} /></IntlProvider>);
|
||||
|
||||
const render = () => {
|
||||
el = shallow(<EligibleContent cardId={cardId} />);
|
||||
};
|
||||
const loadComponent = () => {
|
||||
component = el.instance.findByType('CreditContent');
|
||||
};
|
||||
describe('EligibleContent component', () => {
|
||||
describe('hooks', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
describe('behavior', () => {
|
||||
it('initializes credit data with cardId', () => {
|
||||
renderEligibleContent();
|
||||
expect(reduxHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
it('initializes course run data with cardId', () => {
|
||||
renderEligibleContent();
|
||||
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
});
|
||||
describe('behavior', () => {
|
||||
describe('render', () => {
|
||||
describe('rendered CreditContent component', () => {
|
||||
it('action message is formatted getCredit message', () => {
|
||||
renderEligibleContent();
|
||||
const button = screen.getByRole('button', { name: messages.getCredit.defaultMessage });
|
||||
expect(button).toBeInTheDocument();
|
||||
beforeEach(() => {
|
||||
loadComponent();
|
||||
});
|
||||
it('onClick sends credit purchase track event', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderEligibleContent();
|
||||
const button = screen.getByRole('button', { name: messages.getCredit.defaultMessage });
|
||||
await user.click(button);
|
||||
expect(track.credit.purchase).toHaveBeenCalledWith(courseId);
|
||||
test('action.onClick sends credit purchase track event', () => {
|
||||
expect(component[0].props.action.onClick).toEqual(
|
||||
track.credit.purchase(courseId),
|
||||
);
|
||||
});
|
||||
it('message is formatted eligible message if provider', () => {
|
||||
renderEligibleContent();
|
||||
const eligibleMessage = screen.getByTestId('credit-msg');
|
||||
expect(eligibleMessage).toBeInTheDocument();
|
||||
expect(eligibleMessage).toHaveTextContent(credit.providerName);
|
||||
test('action.message is formatted getCredit message', () => {
|
||||
expect(component[0].props.action.message).toEqual(formatMessage(messages.getCredit));
|
||||
});
|
||||
it('message is formatted eligible message if no provider', () => {
|
||||
reduxHooks.useCardCreditData.mockReturnValue({});
|
||||
renderEligibleContent();
|
||||
const eligibleMessage = screen.getByTestId('credit-msg');
|
||||
expect(eligibleMessage).toBeInTheDocument();
|
||||
expect(eligibleMessage).toHaveTextContent(messages.getCredit.defaultMessage);
|
||||
test('message is formatted eligible message if no provider', () => {
|
||||
reduxHooks.useCardCreditData.mockReturnValueOnce({});
|
||||
render();
|
||||
loadComponent();
|
||||
expect(component[0].props.message).toEqual(formatMessage(
|
||||
messages.eligible,
|
||||
{ getCredit: (<b>{formatMessage(messages.getCredit)}</b>) },
|
||||
));
|
||||
});
|
||||
test('message is formatted eligible message if provider', () => {
|
||||
expect(component[0].props.message).toEqual(
|
||||
formatMessage(messages.eligibleFromProvider, { providerName: credit.providerName }),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,103 +1,73 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { formatMessage } from 'testUtils';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import messages from './messages';
|
||||
import hooks from './hooks';
|
||||
import ProviderLink from './components/ProviderLink';
|
||||
import MustRequestContent from './MustRequestContent';
|
||||
|
||||
jest.mock('./hooks', () => ({
|
||||
useCreditRequestData: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useMasqueradeData: jest.fn(),
|
||||
useCardCreditData: jest.fn(),
|
||||
},
|
||||
reduxHooks: { useMasqueradeData: jest.fn() },
|
||||
}));
|
||||
jest.mock('./components/CreditContent', () => 'CreditContent');
|
||||
jest.mock('./components/ProviderLink', () => 'ProviderLink');
|
||||
|
||||
let el;
|
||||
let component;
|
||||
|
||||
const cardId = 'test-card-id';
|
||||
const requestData = {
|
||||
url: 'test-request-data-url',
|
||||
parameters: {
|
||||
key1: 'val1',
|
||||
key2: 'val2',
|
||||
key3: 'val3',
|
||||
},
|
||||
};
|
||||
const providerName = 'test-credit-provider-name';
|
||||
const providerStatusUrl = 'test-credit-provider-status-url';
|
||||
const requestData = { test: 'requestData' };
|
||||
const createCreditRequest = jest.fn().mockName('createCreditRequest');
|
||||
hooks.useCreditRequestData.mockReturnValue({
|
||||
requestData,
|
||||
createCreditRequest,
|
||||
});
|
||||
reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: false });
|
||||
|
||||
const renderMustRequestContent = () => render(
|
||||
<IntlProvider locale="en" messages={messages}>
|
||||
<MustRequestContent cardId={cardId} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const render = () => {
|
||||
el = shallow(<MustRequestContent cardId={cardId} />);
|
||||
};
|
||||
describe('MustRequestContent component', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
hooks.useCreditRequestData.mockReturnValue({
|
||||
requestData,
|
||||
createCreditRequest,
|
||||
});
|
||||
reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: false });
|
||||
reduxHooks.useCardCreditData.mockReturnValue({
|
||||
providerName,
|
||||
providerStatusUrl,
|
||||
});
|
||||
render();
|
||||
});
|
||||
|
||||
describe('hooks', () => {
|
||||
describe('behavior', () => {
|
||||
it('initializes credit request data with cardId', () => {
|
||||
renderMustRequestContent();
|
||||
expect(hooks.useCreditRequestData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('behavior', () => {
|
||||
describe('rendered content', () => {
|
||||
describe('render', () => {
|
||||
describe('rendered CreditContent component', () => {
|
||||
beforeEach(() => {
|
||||
renderMustRequestContent();
|
||||
component = el.instance.findByType('CreditContent');
|
||||
});
|
||||
|
||||
it('calls createCreditRequest when request credit button is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
const button = screen.getByRole('button', { name: /request credit/i });
|
||||
await user.click(button);
|
||||
expect(createCreditRequest).toHaveBeenCalled();
|
||||
test('action.onClick calls createCreditRequest from useCreditRequestData hook', () => {
|
||||
expect(component[0].props.action.onClick).toEqual(createCreditRequest);
|
||||
});
|
||||
|
||||
it('shows request credit button that is enabled', () => {
|
||||
const button = screen.getByRole('button', { name: /request credit/i });
|
||||
expect(button).toBeEnabled();
|
||||
test('action.message is formatted requestCredit message', () => {
|
||||
expect(component[0].props.action.message).toEqual(
|
||||
formatMessage(messages.requestCredit),
|
||||
);
|
||||
});
|
||||
|
||||
it('displays must request message with provider link', () => {
|
||||
expect(screen.getByTestId('credit-msg')).toHaveTextContent(/request credit/i);
|
||||
test('action.disabled is false', () => {
|
||||
expect(component[0].props.action.disabled).toEqual(false);
|
||||
});
|
||||
|
||||
it('renders credit request form with correct data', () => {
|
||||
const { container } = renderMustRequestContent();
|
||||
const form = container.querySelector('form');
|
||||
expect(form).toBeInTheDocument();
|
||||
expect(form).toHaveAttribute('action', requestData.url);
|
||||
test('message is formatted mustRequest message', () => {
|
||||
expect(component[0].props.message).toEqual(
|
||||
formatMessage(messages.mustRequest, {
|
||||
linkToProviderSite: <ProviderLink cardId={cardId} />,
|
||||
requestCredit: <b>{formatMessage(messages.requestCredit)}</b>,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when masquerading', () => {
|
||||
beforeEach(() => {
|
||||
reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: true });
|
||||
renderMustRequestContent();
|
||||
});
|
||||
|
||||
it('disables the request credit button', () => {
|
||||
const button = screen.getByRole('button', { name: /request credit/i });
|
||||
expect(button).toHaveClass('disabled');
|
||||
expect(button).toHaveAttribute('aria-disabled', 'true');
|
||||
test('requestData drawn from useCreditRequestData hook', () => {
|
||||
expect(component[0].props.requestData).toEqual(requestData);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { formatMessage } from 'testUtils';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import messages from './messages';
|
||||
@@ -9,6 +10,11 @@ import PendingContent from './PendingContent';
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: { useCardCreditData: jest.fn(), useMasqueradeData: jest.fn() },
|
||||
}));
|
||||
jest.mock('./components/CreditContent', () => 'CreditContent');
|
||||
jest.mock('./components/ProviderLink', () => 'ProviderLink');
|
||||
|
||||
let el;
|
||||
let component;
|
||||
|
||||
const cardId = 'test-card-id';
|
||||
const providerName = 'test-credit-provider-name';
|
||||
@@ -19,48 +25,38 @@ reduxHooks.useCardCreditData.mockReturnValue({
|
||||
});
|
||||
reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: false });
|
||||
|
||||
const renderPendingContent = () => render(
|
||||
<IntlProvider messages={{}} locale="en">
|
||||
<PendingContent cardId={cardId} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
const render = () => {
|
||||
el = shallow(<PendingContent cardId={cardId} />);
|
||||
};
|
||||
describe('PendingContent component', () => {
|
||||
describe('hooks', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
describe('behavior', () => {
|
||||
it('initializes card credit data with cardId', () => {
|
||||
renderPendingContent();
|
||||
expect(reduxHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
});
|
||||
describe('behavior', () => {
|
||||
describe('render', () => {
|
||||
describe('rendered CreditContent component', () => {
|
||||
it('action message is formatted requestCredit message', () => {
|
||||
renderPendingContent();
|
||||
const button = screen.getByRole('link', { name: messages.viewDetails.defaultMessage });
|
||||
expect(button).toBeInTheDocument();
|
||||
beforeEach(() => {
|
||||
component = el.instance.findByType('CreditContent');
|
||||
});
|
||||
it('action href will go to provider status site', () => {
|
||||
renderPendingContent();
|
||||
const button = screen.getByRole('link', { name: messages.viewDetails.defaultMessage });
|
||||
expect(button).toHaveAttribute('href', providerStatusUrl);
|
||||
test('action.href will go to provider status site', () => {
|
||||
expect(component[0].props.action.href).toEqual(providerStatusUrl);
|
||||
});
|
||||
it('action.disabled is false', () => {
|
||||
renderPendingContent();
|
||||
const button = screen.getByRole('link', { name: messages.viewDetails.defaultMessage });
|
||||
expect(button).not.toHaveClass('disabled');
|
||||
test('action.message is formatted requestCredit message', () => {
|
||||
expect(component[0].props.action.message).toEqual(
|
||||
formatMessage(messages.viewDetails),
|
||||
);
|
||||
});
|
||||
it('message is formatted pending message with provider name', () => {
|
||||
renderPendingContent();
|
||||
const component = screen.getByTestId('credit-msg');
|
||||
expect(component).toBeInTheDocument();
|
||||
expect(component).toHaveTextContent(`${providerName} has received`);
|
||||
test('action.disabled is false', () => {
|
||||
expect(component[0].props.action.disabled).toEqual(false);
|
||||
});
|
||||
describe('when masqueradeData is true', () => {
|
||||
it('disables the view details button', () => {
|
||||
reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: true });
|
||||
renderPendingContent();
|
||||
const button = screen.getByRole('link', { name: messages.viewDetails.defaultMessage });
|
||||
expect(button).toHaveClass('disabled');
|
||||
});
|
||||
test('message is formatted pending message', () => {
|
||||
expect(component[0].props.message).toEqual(
|
||||
formatMessage(messages.received, { providerName }),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { formatMessage } from 'testUtils';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import messages from './messages';
|
||||
import ProviderLink from './components/ProviderLink';
|
||||
import RejectedContent from './RejectedContent';
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
@@ -9,6 +12,8 @@ jest.mock('hooks', () => ({
|
||||
useCardCreditData: jest.fn(),
|
||||
},
|
||||
}));
|
||||
jest.mock('./components/CreditContent', () => 'CreditContent');
|
||||
jest.mock('./components/ProviderLink', () => 'ProviderLink');
|
||||
|
||||
const cardId = 'test-card-id';
|
||||
const credit = {
|
||||
@@ -17,27 +22,32 @@ const credit = {
|
||||
};
|
||||
reduxHooks.useCardCreditData.mockReturnValue(credit);
|
||||
|
||||
const renderRejectedContent = () => render(<IntlProvider><RejectedContent cardId={cardId} /></IntlProvider>);
|
||||
let el;
|
||||
let component;
|
||||
const render = () => { el = shallow(<RejectedContent cardId={cardId} />); };
|
||||
const loadComponent = () => { component = el.instance.findByType('CreditContent'); };
|
||||
|
||||
describe('RejectedContent component', () => {
|
||||
describe('hooks', () => {
|
||||
beforeEach(render);
|
||||
describe('behavior', () => {
|
||||
it('initializes credit data with cardId', () => {
|
||||
renderRejectedContent();
|
||||
expect(reduxHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
});
|
||||
describe('render', () => {
|
||||
describe('rendered CreditContent component', () => {
|
||||
it('no action is passed', () => {
|
||||
renderRejectedContent();
|
||||
const action = screen.queryByTestId('action-row-btn');
|
||||
expect(action).not.toBeInTheDocument();
|
||||
beforeAll(loadComponent);
|
||||
test('no action is passed', () => {
|
||||
expect(component[0].props.action).toEqual(undefined);
|
||||
});
|
||||
it('message is formatted rejected message', () => {
|
||||
renderRejectedContent();
|
||||
const message = screen.getByTestId('credit-msg');
|
||||
expect(message).toBeInTheDocument();
|
||||
expect(message).toHaveTextContent(`${credit.providerName} did not approve your request for course credit.`);
|
||||
test('message is formatted rejected message', () => {
|
||||
expect(component[0].props.message).toEqual(formatMessage(
|
||||
messages.rejected,
|
||||
{
|
||||
linkToProviderSite: <ProviderLink cardId={cardId} />,
|
||||
providerName: credit.providerName,
|
||||
},
|
||||
));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import CreditContent from './CreditContent';
|
||||
|
||||
let el;
|
||||
const action = {
|
||||
href: 'test-action-href',
|
||||
onClick: jest.fn().mockName('test-action-onClick'),
|
||||
@@ -13,57 +15,45 @@ const message = 'test-message';
|
||||
const requestData = { url: 'test-request-data-url', parameters: { key1: 'val1' } };
|
||||
const props = { action, message, requestData };
|
||||
|
||||
const renderCreditContent = (data) => render(
|
||||
<CreditContent {...data} />,
|
||||
);
|
||||
|
||||
describe('CreditContent component', () => {
|
||||
describe('render', () => {
|
||||
describe('with action', () => {
|
||||
it('loads href and message into action row button', () => {
|
||||
renderCreditContent(props);
|
||||
const button = screen.getByRole('link', { name: action.message });
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).toHaveAttribute('href', action.href);
|
||||
expect(button).not.toHaveAttribute('disabled');
|
||||
beforeEach(() => {
|
||||
el = shallow(<CreditContent {...props} />);
|
||||
});
|
||||
test('snapshot', () => {
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
it('loads href, onClick, and message into action row button', () => {
|
||||
const buttonEl = el.instance.findByTestId('action-row-btn')[0];
|
||||
expect(buttonEl.props.href).toEqual(action.href);
|
||||
expect(buttonEl.props.onClick).toEqual(action.onClick);
|
||||
expect(buttonEl.props.disabled).toEqual(action.disabled);
|
||||
expect(buttonEl.children[0].el).toEqual(action.message);
|
||||
});
|
||||
it('loads message into credit-msg div', () => {
|
||||
renderCreditContent(props);
|
||||
const creditMsg = screen.getByTestId('credit-msg');
|
||||
expect(creditMsg).toBeInTheDocument();
|
||||
expect(creditMsg.innerHTML).toEqual(message);
|
||||
expect(el.instance.findByTestId('credit-msg')[0].children[0].el).toEqual(message);
|
||||
});
|
||||
it('loads CreditRequestForm with passed requestData', () => {
|
||||
const { container } = renderCreditContent(props);
|
||||
const creditForm = container.querySelector('form');
|
||||
expect(creditForm).toBeInTheDocument();
|
||||
expect(creditForm).toHaveAttribute('action', requestData.url);
|
||||
expect(el.instance.findByType('CreditRequestForm')[0].props.requestData).toEqual(requestData);
|
||||
});
|
||||
it('disables action button when action.disabled is true', () => {
|
||||
renderCreditContent({ ...props, action: { ...action, disabled: true } });
|
||||
const button = screen.getByRole('link', { name: action.message });
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).toHaveClass('disabled');
|
||||
expect(button).toHaveAttribute('aria-disabled', 'true');
|
||||
test('disables action button when action.disabled is true', () => {
|
||||
el = shallow(<CreditContent {...props} action={{ ...action, disabled: true }} />);
|
||||
expect(el.instance.findByTestId('action-row-btn')[0].props.disabled).toEqual(true);
|
||||
});
|
||||
});
|
||||
describe('without action', () => {
|
||||
test('snapshot', () => {
|
||||
el = shallow(<CreditContent {...{ message, requestData }} />);
|
||||
});
|
||||
test('snapshot', () => {
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
it('loads message into credit-msg div', () => {
|
||||
renderCreditContent({ message, requestData });
|
||||
const creditMsg = screen.getByTestId('credit-msg');
|
||||
expect(creditMsg).toBeInTheDocument();
|
||||
expect(creditMsg.innerHTML).toEqual(message);
|
||||
expect(el.instance.findByTestId('credit-msg')[0].children[0].el).toEqual(message);
|
||||
});
|
||||
it('loads CreditRequestForm with passed requestData', () => {
|
||||
const { container } = renderCreditContent({ message, requestData });
|
||||
const creditForm = container.querySelector('form');
|
||||
expect(creditForm).toBeInTheDocument();
|
||||
expect(creditForm).toHaveAttribute('action', requestData.url);
|
||||
});
|
||||
it('does not render action row button', () => {
|
||||
renderCreditContent({ message, requestData });
|
||||
const button = screen.queryByRole('link', { name: action.message });
|
||||
expect(button).not.toBeInTheDocument();
|
||||
expect(el.instance.findByType('CreditRequestForm')[0].props.requestData).toEqual(requestData);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CreditRequestForm component render output valid requestData snapshot 1`] = `
|
||||
<Form
|
||||
accept-method="UTF-8"
|
||||
action="test-request-data-url"
|
||||
className="hidden"
|
||||
method="POST"
|
||||
>
|
||||
<FormControl
|
||||
as="textarea"
|
||||
key="key1"
|
||||
name="key1"
|
||||
value="val1"
|
||||
/>
|
||||
<FormControl
|
||||
as="textarea"
|
||||
key="key2"
|
||||
name="key2"
|
||||
value="val2"
|
||||
/>
|
||||
<FormControl
|
||||
as="textarea"
|
||||
key="key3"
|
||||
name="key3"
|
||||
value="val3"
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
/>
|
||||
</Form>
|
||||
`;
|
||||
@@ -4,12 +4,6 @@ import useCreditRequestFormData from './hooks';
|
||||
|
||||
const requestData = 'test-request-data';
|
||||
|
||||
jest.mock('react', () => ({
|
||||
...jest.requireActual('react'),
|
||||
useRef: jest.fn((val) => ({ current: val, useRef: true })),
|
||||
useEffect: jest.fn((cb, prereqs) => ({ useEffect: { cb, prereqs } })),
|
||||
}));
|
||||
|
||||
let out;
|
||||
const ref = {
|
||||
current: { click: jest.fn() },
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { keyStore } from 'utils';
|
||||
|
||||
@@ -10,8 +11,7 @@ jest.mock('./hooks', () => ({
|
||||
default: jest.fn(),
|
||||
}));
|
||||
|
||||
const ref = { current: { click: jest.fn() }, useRef: jest.fn() };
|
||||
|
||||
const ref = 'test-ref';
|
||||
const requestData = {
|
||||
url: 'test-request-data-url',
|
||||
parameters: {
|
||||
@@ -25,41 +25,40 @@ const paramKeys = keyStore(requestData.parameters);
|
||||
|
||||
useCreditRequestFormData.mockReturnValue({ ref });
|
||||
|
||||
const renderForm = (data) => render(<CreditRequestForm requestData={data} />);
|
||||
let el;
|
||||
const shallowRender = (data) => { el = shallow(<CreditRequestForm requestData={data} />); };
|
||||
describe('CreditRequestForm component', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
describe('hooks', () => {
|
||||
describe('behavior', () => {
|
||||
it('initializes ref from hook with requestData', () => {
|
||||
renderForm(requestData);
|
||||
shallowRender(requestData);
|
||||
expect(useCreditRequestFormData).toHaveBeenCalledWith(requestData);
|
||||
});
|
||||
});
|
||||
describe('render output', () => {
|
||||
describe('null requestData', () => {
|
||||
it('returns null', () => {
|
||||
const { container } = renderForm(null);
|
||||
expect(container.firstChild).toBeNull();
|
||||
shallowRender(null);
|
||||
expect(el.isEmptyRender()).toEqual(true);
|
||||
});
|
||||
});
|
||||
describe('valid requestData', () => {
|
||||
beforeEach(() => {
|
||||
shallowRender(requestData);
|
||||
});
|
||||
test('snapshot', () => {
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
it('loads Form with requestData url', () => {
|
||||
const { container } = renderForm(requestData);
|
||||
const creditForm = container.querySelector('form');
|
||||
expect(creditForm).toBeInTheDocument();
|
||||
expect(creditForm).toHaveAttribute('action', requestData.url);
|
||||
expect(el.instance.findByType('Form')[0].props.action).toEqual(requestData.url);
|
||||
});
|
||||
it('loads a textarea form control for each requestData parameter', () => {
|
||||
const { container } = renderForm(requestData);
|
||||
const controls = container.querySelectorAll('textarea');
|
||||
expect(controls.length).toEqual(Object.keys(requestData.parameters).length);
|
||||
expect(controls[0]).toHaveAttribute('name', paramKeys.key1);
|
||||
expect(controls[0]).toHaveValue(requestData.parameters.key1);
|
||||
expect(controls[1]).toHaveAttribute('name', paramKeys.key2);
|
||||
expect(controls[1]).toHaveValue(requestData.parameters.key2);
|
||||
expect(controls[2]).toHaveAttribute('name', paramKeys.key3);
|
||||
expect(controls[2]).toHaveValue(requestData.parameters.key3);
|
||||
const controls = el.instance.findByType('FormControl');
|
||||
expect(controls[0].props.name).toEqual(paramKeys.key1);
|
||||
expect(controls[0].props.value).toEqual(requestData.parameters.key1);
|
||||
expect(controls[1].props.name).toEqual(paramKeys.key2);
|
||||
expect(controls[1].props.value).toEqual(requestData.parameters.key2);
|
||||
expect(controls[2].props.name).toEqual(paramKeys.key3);
|
||||
expect(controls[2].props.value).toEqual(requestData.parameters.key3);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,9 @@ import { render } from '@testing-library/react';
|
||||
import useCreditRequestFormData from './hooks';
|
||||
import CreditRequestForm from '.';
|
||||
|
||||
jest.unmock('@openedx/paragon');
|
||||
jest.unmock('react');
|
||||
|
||||
jest.mock('./hooks', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import ProviderLink from './ProviderLink';
|
||||
|
||||
@@ -15,30 +16,27 @@ const credit = {
|
||||
providerStatusUrl: 'test-credit-provider-status-url',
|
||||
providerName: 'test-credit-provider-name',
|
||||
};
|
||||
|
||||
const renderProviderLink = () => render(
|
||||
<IntlProvider locale="en"><ProviderLink cardId={cardId} /></IntlProvider>,
|
||||
);
|
||||
let el;
|
||||
|
||||
describe('ProviderLink component', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
reduxHooks.useCardCreditData.mockReturnValue(credit);
|
||||
renderProviderLink();
|
||||
el = shallow(<ProviderLink cardId={cardId} />);
|
||||
});
|
||||
describe('hooks', () => {
|
||||
describe('behavior', () => {
|
||||
it('initializes credit hook with cardId', () => {
|
||||
expect(reduxHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
});
|
||||
describe('render', () => {
|
||||
test('snapshot', () => {
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
it('passes credit.providerStatusUrl to the hyperlink href', () => {
|
||||
const providerLink = screen.getByRole('link', { href: credit.providerStatusUrl });
|
||||
expect(providerLink).toBeInTheDocument();
|
||||
expect(el.instance.findByType('Hyperlink')[0].props.href).toEqual(credit.providerStatusUrl);
|
||||
});
|
||||
it('passes providerName for the link message', () => {
|
||||
const providerLink = screen.getByRole('link', { href: credit.providerStatusUrl });
|
||||
expect(providerLink).toHaveTextContent(credit.providerName);
|
||||
expect(el.instance.findByType('Hyperlink')[0].children[0].el).toEqual(credit.providerName);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CreditContent component render with action snapshot 1`] = `
|
||||
<Fragment>
|
||||
<div
|
||||
className="message-copy credit-msg"
|
||||
data-testid="credit-msg"
|
||||
>
|
||||
test-message
|
||||
</div>
|
||||
<ActionRow
|
||||
className="mt-4"
|
||||
>
|
||||
<Button
|
||||
as="a"
|
||||
className="border-gray-400"
|
||||
data-testid="action-row-btn"
|
||||
disabled={false}
|
||||
href="test-action-href"
|
||||
onClick={[MockFunction test-action-onClick]}
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
variant="outline-primary"
|
||||
>
|
||||
test-action-message
|
||||
</Button>
|
||||
</ActionRow>
|
||||
<CreditRequestForm
|
||||
requestData={
|
||||
{
|
||||
"parameters": {
|
||||
"key1": "val1",
|
||||
},
|
||||
"url": "test-request-data-url",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`CreditContent component render without action snapshot 1`] = `
|
||||
<Fragment>
|
||||
<div
|
||||
className="message-copy credit-msg"
|
||||
data-testid="credit-msg"
|
||||
>
|
||||
test-message
|
||||
</div>
|
||||
<CreditRequestForm
|
||||
requestData={
|
||||
{
|
||||
"parameters": {
|
||||
"key1": "val1",
|
||||
},
|
||||
"url": "test-request-data-url",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
@@ -0,0 +1,11 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ProviderLink component render snapshot 1`] = `
|
||||
<Hyperlink
|
||||
href="test-credit-provider-status-url"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
test-credit-provider-name
|
||||
</Hyperlink>
|
||||
`;
|
||||
@@ -1,11 +1,10 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { formatMessage } from 'testUtils';
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import EntitlementBanner from './EntitlementBanner';
|
||||
import messages from './messages';
|
||||
|
||||
jest.mock('components/Banner', () => 'Banner');
|
||||
jest.mock('hooks', () => ({
|
||||
utilHooks: {
|
||||
useFormatDate: () => date => date,
|
||||
@@ -19,7 +18,9 @@ jest.mock('hooks', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const cardId = 'test-card-id';
|
||||
const cardId = 'my-test-course-number';
|
||||
|
||||
let el;
|
||||
|
||||
const entitlementData = {
|
||||
isEntitlement: true,
|
||||
@@ -30,43 +31,33 @@ const entitlementData = {
|
||||
};
|
||||
const platformData = { supportEmail: 'test-support-email' };
|
||||
|
||||
const renderComponent = (overrides = {}) => {
|
||||
const render = (overrides = {}) => {
|
||||
const { entitlement = {} } = overrides;
|
||||
reduxHooks.useCardEntitlementData.mockReturnValueOnce({ ...entitlementData, ...entitlement });
|
||||
reduxHooks.usePlatformSettingsData.mockReturnValueOnce(platformData);
|
||||
return render(<IntlProvider locale="en"><EntitlementBanner cardId={cardId} /></IntlProvider>);
|
||||
el = shallow(<EntitlementBanner cardId={cardId} />);
|
||||
};
|
||||
|
||||
describe('EntitlementBanner', () => {
|
||||
it('initializes data with course number from entitlement', () => {
|
||||
renderComponent();
|
||||
test('initializes data with course number from entitlement', () => {
|
||||
render();
|
||||
expect(reduxHooks.useCardEntitlementData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useUpdateSelectSessionModalCallback).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
it('no display if not an entitlement', () => {
|
||||
renderComponent({ entitlement: { isEntitlement: false } });
|
||||
const banner = screen.queryByRole('alert');
|
||||
expect(banner).toBeNull();
|
||||
test('no display if not an entitlement', () => {
|
||||
render({ entitlement: { isEntitlement: false } });
|
||||
expect(el.isEmptyRender()).toEqual(true);
|
||||
});
|
||||
it('renders when no sessions available', () => {
|
||||
renderComponent({ entitlement: { isFulfilled: false, hasSessions: false } });
|
||||
const banner = screen.getByRole('alert');
|
||||
expect(banner).toBeInTheDocument();
|
||||
expect(banner).toHaveClass('alert-warning');
|
||||
expect(banner.innerHTML).toContain(platformData.supportEmail);
|
||||
test('snapshot: no sessions available', () => {
|
||||
render({ entitlement: { isFulfilled: false, hasSessions: false } });
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
it('renders when expiration warning', () => {
|
||||
renderComponent({ entitlement: { showExpirationWarning: true } });
|
||||
const banner = screen.getByRole('alert');
|
||||
expect(banner).toBeInTheDocument();
|
||||
expect(banner).toHaveClass('alert-info');
|
||||
const button = screen.getByRole('button', { name: formatMessage(messages.selectSession) });
|
||||
expect(button).toBeInTheDocument();
|
||||
test('snapshot: expiration warning', () => {
|
||||
render({ entitlement: { showExpirationWarning: true } });
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
it('renders expired banner', () => {
|
||||
renderComponent({ entitlement: { isExpired: true } });
|
||||
const banner = screen.getByRole('alert');
|
||||
expect(banner).toBeInTheDocument();
|
||||
expect(banner.innerHTML).toContain(formatMessage(messages.entitlementExpired));
|
||||
test('no display if sessions available and not displaying warning', () => {
|
||||
render();
|
||||
expect(el.isEmptyRender()).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { ProgramsList } from './ProgramsList';
|
||||
|
||||
@@ -9,23 +9,15 @@ describe('ProgramsList', () => {
|
||||
title: 'Example Program 1',
|
||||
},
|
||||
{
|
||||
programUrl: 'http://example2.com',
|
||||
programUrl: 'http://example.com',
|
||||
title: 'Example Program 2',
|
||||
},
|
||||
];
|
||||
|
||||
it('renders correctly', () => {
|
||||
render(<ProgramsList programs={programs} />);
|
||||
const list = screen.getByRole('list');
|
||||
expect(list).toBeInTheDocument();
|
||||
expect(list.children.length).toEqual(programs.length);
|
||||
});
|
||||
const wrapper = shallow(<ProgramsList programs={programs} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
|
||||
it('add the links correctly', () => {
|
||||
render(<ProgramsList programs={programs} />);
|
||||
programs.forEach(program => {
|
||||
const link = screen.getByRole('link', { name: program.title });
|
||||
expect(link).toHaveAttribute('href', program.url);
|
||||
});
|
||||
expect(wrapper.instance.findByType('li').length).toEqual(programs.length);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ProgramsList renders correctly 1`] = `
|
||||
<ul
|
||||
className="related-programs-list-container"
|
||||
>
|
||||
<li
|
||||
className="my-2"
|
||||
key="http://example.com"
|
||||
>
|
||||
<a
|
||||
href="http://example.com"
|
||||
>
|
||||
Example Program 1
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className="my-2"
|
||||
key="http://example.com"
|
||||
>
|
||||
<a
|
||||
href="http://example.com"
|
||||
>
|
||||
Example Program 2
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
`;
|
||||
@@ -0,0 +1,29 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`RelatedProgramsBanner render with programs 1`] = `
|
||||
<Banner
|
||||
className="bg-white border-top border-bottom mb-0 related-programs-banner"
|
||||
icon={[MockFunction icons.Program]}
|
||||
variant="info"
|
||||
>
|
||||
<span
|
||||
className="font-weight-bolder"
|
||||
>
|
||||
Related Programs:
|
||||
</span>
|
||||
<ProgramsList
|
||||
programs={
|
||||
[
|
||||
{
|
||||
"title": "Program 1",
|
||||
"url": "http://example.com/program1",
|
||||
},
|
||||
{
|
||||
"title": "Program 2",
|
||||
"url": "http://example.com/program2",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Banner>
|
||||
`;
|
||||
@@ -1,9 +1,10 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import RelatedProgramsBanner from '.';
|
||||
|
||||
jest.mock('./ProgramsList', () => 'ProgramsList');
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardRelatedProgramsData: jest.fn(),
|
||||
@@ -11,39 +12,31 @@ jest.mock('hooks', () => ({
|
||||
}));
|
||||
|
||||
const cardId = 'test-card-id';
|
||||
const programData = {
|
||||
list: [
|
||||
{
|
||||
title: 'Program 1',
|
||||
url: 'http://example.com/program1',
|
||||
},
|
||||
{
|
||||
title: 'Program 2',
|
||||
url: 'http://example.com/program2',
|
||||
},
|
||||
],
|
||||
length: 2,
|
||||
};
|
||||
|
||||
describe('RelatedProgramsBanner', () => {
|
||||
it('render empty', () => {
|
||||
reduxHooks.useCardRelatedProgramsData.mockReturnValue({});
|
||||
render(<IntlProvider locale="en"><RelatedProgramsBanner cardId={cardId} /></IntlProvider>);
|
||||
const banner = screen.queryByRole('alert');
|
||||
expect(banner).toBeNull();
|
||||
test('render empty', () => {
|
||||
reduxHooks.useCardRelatedProgramsData.mockReturnValue({
|
||||
length: 0,
|
||||
});
|
||||
const el = shallow(<RelatedProgramsBanner cardId={cardId} />);
|
||||
expect(el.isEmptyRender()).toEqual(true);
|
||||
});
|
||||
|
||||
it('render with programs', () => {
|
||||
reduxHooks.useCardRelatedProgramsData.mockReturnValue(programData);
|
||||
render(<IntlProvider locale="en"><RelatedProgramsBanner cardId={cardId} /></IntlProvider>);
|
||||
const list = screen.getByRole('list');
|
||||
expect(list.childElementCount).toBe(programData.list.length);
|
||||
});
|
||||
|
||||
it('render related programs title', () => {
|
||||
reduxHooks.useCardRelatedProgramsData.mockReturnValue(programData);
|
||||
render(<IntlProvider locale="en"><RelatedProgramsBanner cardId={cardId} /></IntlProvider>);
|
||||
const title = screen.getByText('Related Programs:');
|
||||
expect(title).toBeInTheDocument();
|
||||
test('render with programs', () => {
|
||||
reduxHooks.useCardRelatedProgramsData.mockReturnValue({
|
||||
list: [
|
||||
{
|
||||
title: 'Program 1',
|
||||
url: 'http://example.com/program1',
|
||||
},
|
||||
{
|
||||
title: 'Program 2',
|
||||
url: 'http://example.com/program2',
|
||||
},
|
||||
],
|
||||
length: 2,
|
||||
});
|
||||
const el = shallow(<RelatedProgramsBanner cardId={cardId} />);
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CertificateBanner snapshot is passing and is downloadable 1`] = `
|
||||
<Banner
|
||||
icon={[MockFunction icons.CheckCircle]}
|
||||
variant="success"
|
||||
>
|
||||
Congratulations. Your certificate is ready.
|
||||
</Banner>
|
||||
`;
|
||||
|
||||
exports[`CertificateBanner snapshot is passing and is earned but unavailable 1`] = `
|
||||
<Banner>
|
||||
Your grade and certificate will be ready after 10/20/3030.
|
||||
</Banner>
|
||||
`;
|
||||
|
||||
exports[`CertificateBanner snapshot is passing and not downloadable render empty 1`] = `null`;
|
||||
|
||||
exports[`CertificateBanner snapshot is restricted 1`] = `
|
||||
<Banner
|
||||
variant="danger"
|
||||
>
|
||||
Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know.
|
||||
</Banner>
|
||||
`;
|
||||
|
||||
exports[`CertificateBanner snapshot is restricted and verified 1`] = `
|
||||
<Banner
|
||||
variant="danger"
|
||||
>
|
||||
Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know.
|
||||
|
||||
If you would like a refund on your Certificate of Achievement, please contact us.
|
||||
</Banner>
|
||||
`;
|
||||
|
||||
exports[`CertificateBanner snapshot is restricted and verified with billing email 1`] = `
|
||||
<Banner
|
||||
variant="danger"
|
||||
>
|
||||
Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know.
|
||||
|
||||
<format-message-function
|
||||
message={
|
||||
{
|
||||
"defaultMessage": "If you would like a refund on your Certificate of Achievement, please contact our billing address {billingEmail}",
|
||||
"description": "Message to learners to contact billing for certificate refunds",
|
||||
"id": "learner-dash.courseCard.banners.certificateRefundContactBilling",
|
||||
}
|
||||
}
|
||||
values={
|
||||
{
|
||||
"billingEmail": <MailtoLink
|
||||
to="billing@email"
|
||||
>
|
||||
billing@email
|
||||
</MailtoLink>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Banner>
|
||||
`;
|
||||
|
||||
exports[`CertificateBanner snapshot is restricted and verified with support and billing email 1`] = `
|
||||
<Banner
|
||||
variant="danger"
|
||||
>
|
||||
<format-message-function
|
||||
message={
|
||||
{
|
||||
"defaultMessage": "Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting {supportEmail}.",
|
||||
"description": "Restricted certificate warning message",
|
||||
"id": "learner-dash.courseCard.banners.certificateRestricted",
|
||||
}
|
||||
}
|
||||
values={
|
||||
{
|
||||
"supportEmail": <MailtoLink
|
||||
to="suport@email"
|
||||
>
|
||||
suport@email
|
||||
</MailtoLink>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
<format-message-function
|
||||
message={
|
||||
{
|
||||
"defaultMessage": "If you would like a refund on your Certificate of Achievement, please contact our billing address {billingEmail}",
|
||||
"description": "Message to learners to contact billing for certificate refunds",
|
||||
"id": "learner-dash.courseCard.banners.certificateRefundContactBilling",
|
||||
}
|
||||
}
|
||||
values={
|
||||
{
|
||||
"billingEmail": <MailtoLink
|
||||
to="billing@email"
|
||||
>
|
||||
billing@email
|
||||
</MailtoLink>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Banner>
|
||||
`;
|
||||
|
||||
exports[`CertificateBanner snapshot is restricted and verified with support email 1`] = `
|
||||
<Banner
|
||||
variant="danger"
|
||||
>
|
||||
<format-message-function
|
||||
message={
|
||||
{
|
||||
"defaultMessage": "Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting {supportEmail}.",
|
||||
"description": "Restricted certificate warning message",
|
||||
"id": "learner-dash.courseCard.banners.certificateRestricted",
|
||||
}
|
||||
}
|
||||
values={
|
||||
{
|
||||
"supportEmail": <MailtoLink
|
||||
to="suport@email"
|
||||
>
|
||||
suport@email
|
||||
</MailtoLink>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
If you would like a refund on your Certificate of Achievement, please contact us.
|
||||
</Banner>
|
||||
`;
|
||||
|
||||
exports[`CertificateBanner snapshot is restricted with billing email 1`] = `
|
||||
<Banner
|
||||
variant="danger"
|
||||
>
|
||||
Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know.
|
||||
</Banner>
|
||||
`;
|
||||
|
||||
exports[`CertificateBanner snapshot is restricted with support email 1`] = `
|
||||
<Banner
|
||||
variant="danger"
|
||||
>
|
||||
<format-message-function
|
||||
message={
|
||||
{
|
||||
"defaultMessage": "Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting {supportEmail}.",
|
||||
"description": "Restricted certificate warning message",
|
||||
"id": "learner-dash.courseCard.banners.certificateRestricted",
|
||||
}
|
||||
}
|
||||
values={
|
||||
{
|
||||
"supportEmail": <MailtoLink
|
||||
to="suport@email"
|
||||
>
|
||||
suport@email
|
||||
</MailtoLink>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Banner>
|
||||
`;
|
||||
|
||||
exports[`CertificateBanner snapshot not passing and audit 1`] = `
|
||||
<Banner>
|
||||
Grade required to pass the course: 0.8%
|
||||
</Banner>
|
||||
`;
|
||||
|
||||
exports[`CertificateBanner snapshot not passing and has finished 1`] = `
|
||||
<Banner
|
||||
variant="warning"
|
||||
>
|
||||
You are not eligible for a certificate.
|
||||
|
||||
<Hyperlink
|
||||
destination="progressUrl"
|
||||
isInline={true}
|
||||
>
|
||||
View grades.
|
||||
</Hyperlink>
|
||||
</Banner>
|
||||
`;
|
||||
|
||||
exports[`CertificateBanner snapshot not passing and is downloadable 1`] = `
|
||||
<Banner
|
||||
icon={[MockFunction icons.CheckCircle]}
|
||||
variant="success"
|
||||
>
|
||||
Congratulations. Your certificate is ready.
|
||||
</Banner>
|
||||
`;
|
||||
|
||||
exports[`CertificateBanner snapshot not passing and not audit and not finished 1`] = `
|
||||
<Banner
|
||||
variant="warning"
|
||||
>
|
||||
Grade required for a certificate: 0.8%
|
||||
</Banner>
|
||||
`;
|
||||
@@ -0,0 +1,38 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CourseBanner audit access expired snapshot: (auditAccessExpired, findAnotherCourse hyperlink) 1`] = `
|
||||
<Fragment>
|
||||
<Banner>
|
||||
Your audit access to this course has expired.
|
||||
|
||||
<Hyperlink
|
||||
destination=""
|
||||
isInline={true}
|
||||
>
|
||||
Find another course
|
||||
</Hyperlink>
|
||||
</Banner>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`CourseBanner snapshot: stacking banners 1`] = `<Fragment />`;
|
||||
|
||||
exports[`CourseBanner staff snapshot: isStaff 1`] = `<Fragment />`;
|
||||
|
||||
exports[`CourseBanner too early has start date snapshot 1`] = `
|
||||
<Fragment>
|
||||
<Banner>
|
||||
You can't access this course just yet because the course hasn't started yet. The course will start on 11/11/3030.
|
||||
</Banner>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`CourseBanner too early no start date snapshot 1`] = `<Fragment />`;
|
||||
|
||||
exports[`CourseBanner unmet prerequisites snapshot: unmetPrerequisites 1`] = `
|
||||
<Fragment>
|
||||
<Banner>
|
||||
You can't access this course just yet because you have not met the pre-requisites.
|
||||
</Banner>
|
||||
</Fragment>
|
||||
`;
|
||||
@@ -0,0 +1,53 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`EntitlementBanner snapshot: expiration warning 1`] = `
|
||||
<Banner>
|
||||
<format-message-function
|
||||
message={
|
||||
{
|
||||
"defaultMessage": "You must {selectSessionButton} by {changeDeadline} to access the course.",
|
||||
"description": "Entitlement course message when the entitlement is expiring soon.",
|
||||
"id": "learner-dash.courseCard.banners.entitlementExpiringSoon",
|
||||
}
|
||||
}
|
||||
values={
|
||||
{
|
||||
"changeDeadline": "11/11/2022",
|
||||
"selectSessionButton": <Button
|
||||
className="m-0 p-0"
|
||||
onClick={[MockFunction updateSelectSessionModalCallback(my-test-course-number)]}
|
||||
size="inline"
|
||||
variant="link"
|
||||
>
|
||||
select a session
|
||||
</Button>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Banner>
|
||||
`;
|
||||
|
||||
exports[`EntitlementBanner snapshot: no sessions available 1`] = `
|
||||
<Banner
|
||||
variant="warning"
|
||||
>
|
||||
<format-message-function
|
||||
message={
|
||||
{
|
||||
"defaultMessage": "There are no sessions available at the moment. The course team will create new sessions soon. If no sessions appear, please contact {emailLink} for information.",
|
||||
"description": "Entitlement course message when no sessions are available",
|
||||
"id": "learner-dash.courseCard.banners.entitlementUnavailable",
|
||||
}
|
||||
}
|
||||
values={
|
||||
{
|
||||
"emailLink": <MailtoLink
|
||||
to="test-support-email"
|
||||
>
|
||||
test-support-email
|
||||
</MailtoLink>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Banner>
|
||||
`;
|
||||
@@ -0,0 +1,41 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CourseCardBanners render with isEnrolled false 1`] = `
|
||||
<div
|
||||
className="course-card-banners"
|
||||
data-testid="CourseCardBanners"
|
||||
>
|
||||
<RelatedProgramsBanner
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
<CourseBannerSlot
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
<EntitlementBanner
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`CourseCardBanners renders default CourseCardBanners 1`] = `
|
||||
<div
|
||||
className="course-card-banners"
|
||||
data-testid="CourseCardBanners"
|
||||
>
|
||||
<RelatedProgramsBanner
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
<CourseBannerSlot
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
<EntitlementBanner
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
<CertificateBanner
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
<CreditBanner
|
||||
cardId="test-card-id"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,23 +1,14 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import CourseCardBanners from '.';
|
||||
|
||||
jest.mock('./CourseBanner', () => jest.fn(() => <div>CourseBanner</div>));
|
||||
jest.mock('./CertificateBanner', () => jest.fn(() => <div>CertificateBanner</div>));
|
||||
jest.mock('./CreditBanner', () => jest.fn(() => <div>CreditBanner</div>));
|
||||
jest.mock('./EntitlementBanner', () => jest.fn(() => <div>EntitlementBanner</div>));
|
||||
jest.mock('./RelatedProgramsBanner', () => jest.fn(() => <div>RelatedProgramsBanner</div>));
|
||||
|
||||
const mockedComponents = [
|
||||
'CourseBanner',
|
||||
'CertificateBanner',
|
||||
'CreditBanner',
|
||||
'EntitlementBanner',
|
||||
'RelatedProgramsBanner',
|
||||
];
|
||||
jest.mock('./CourseBanner', () => 'CourseBanner');
|
||||
jest.mock('./CertificateBanner', () => 'CertificateBanner');
|
||||
jest.mock('./CreditBanner', () => 'CreditBanner');
|
||||
jest.mock('./EntitlementBanner', () => 'EntitlementBanner');
|
||||
jest.mock('./RelatedProgramsBanner', () => 'RelatedProgramsBanner');
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
@@ -29,20 +20,13 @@ describe('CourseCardBanners', () => {
|
||||
const props = {
|
||||
cardId: 'test-card-id',
|
||||
};
|
||||
it('renders default CourseCardBanners', () => {
|
||||
render(<IntlProvider locale="en"><CourseCardBanners {...props} /></IntlProvider>);
|
||||
mockedComponents.map((componentName) => {
|
||||
const mockedComponent = screen.getByText(componentName);
|
||||
return expect(mockedComponent).toBeInTheDocument();
|
||||
});
|
||||
test('renders default CourseCardBanners', () => {
|
||||
const wrapper = shallow(<CourseCardBanners {...props} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
it('render with isEnrolled false', () => {
|
||||
test('render with isEnrolled false', () => {
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ isEnrolled: false });
|
||||
render(<IntlProvider locale="en"><CourseCardBanners {...props} /></IntlProvider>);
|
||||
const mockedComponentsIfNotEnrolled = mockedComponents.slice(-2);
|
||||
mockedComponentsIfNotEnrolled.map((componentName) => {
|
||||
const mockedComponent = screen.getByText(componentName);
|
||||
return expect(mockedComponent).toBeInTheDocument();
|
||||
});
|
||||
const wrapper = shallow(<CourseCardBanners {...props} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CourseCard Details component does not have change session button on regular course 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="small"
|
||||
data-testid="CourseCardDetails"
|
||||
>
|
||||
provider-name
|
||||
•
|
||||
test-course-number
|
||||
• access-message
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`CourseCard Details component has change session button on entitlement course 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="small"
|
||||
data-testid="CourseCardDetails"
|
||||
>
|
||||
provider-name
|
||||
•
|
||||
test-course-number
|
||||
• access-message
|
||||
•
|
||||
<button
|
||||
class="m-0 p-0"
|
||||
variant="link"
|
||||
>
|
||||
change-or-leave-session-message
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`CourseCard Details component has change session button on entitlement course but no access message 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="small"
|
||||
data-testid="CourseCardDetails"
|
||||
>
|
||||
provider-name
|
||||
•
|
||||
test-course-number
|
||||
•
|
||||
<button
|
||||
class="m-0 p-0"
|
||||
variant="link"
|
||||
>
|
||||
change-or-leave-session-message
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
@@ -20,16 +20,6 @@ jest.mock('hooks', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-platform/i18n', () => {
|
||||
const { formatMessage } = jest.requireActual('testUtils');
|
||||
return {
|
||||
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||
useIntl: () => ({
|
||||
formatMessage,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const cardId = 'my-test-card-id';
|
||||
const courseNumber = 'test-course-number';
|
||||
const useAccessMessage = 'test-access-message';
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
@import "~@edx/brand/paragon/variables";
|
||||
@import "~@openedx/paragon/scss/core/core";
|
||||
@import "~@edx/brand/paragon/overrides";
|
||||
|
||||
a.course-card-title {
|
||||
color: var(--pgn-color-black);
|
||||
color: $black;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import CourseCardDetails from '.';
|
||||
|
||||
@@ -47,36 +48,23 @@ describe('CourseCard Details component', () => {
|
||||
return separatorsCount;
|
||||
};
|
||||
|
||||
it('has change session button on entitlement course', () => {
|
||||
test('has change session button on entitlement course', () => {
|
||||
const wrapper = createWrapper();
|
||||
const sessionButton = screen.getByRole('button', { name: defaultHooks.changeOrLeaveSessionMessage });
|
||||
expect(sessionButton).toBeInTheDocument();
|
||||
|
||||
const accessMessage = screen.getByText((text) => text.includes(defaultHooks.accessMessage));
|
||||
expect(accessMessage).toBeInTheDocument();
|
||||
expect(wrapper.container).toMatchSnapshot();
|
||||
// it has 3 separator, 4 column
|
||||
expect(fetchSeparators(wrapper)).toBe(3);
|
||||
});
|
||||
|
||||
it('has change session button on entitlement course but no access message', () => {
|
||||
test('has change session button on entitlement course but no access message', () => {
|
||||
const wrapper = createWrapper({ accessMessage: null });
|
||||
const sessionButton = screen.getByRole('button', { name: defaultHooks.changeOrLeaveSessionMessage });
|
||||
expect(sessionButton).toBeInTheDocument();
|
||||
|
||||
const accessMessage = screen.queryByText((text) => text.includes(defaultHooks.accessMessage));
|
||||
expect(accessMessage).toBeNull();
|
||||
|
||||
expect(wrapper.container).toMatchSnapshot();
|
||||
// it has 2 separator, 3 column
|
||||
expect(fetchSeparators(wrapper)).toBe(2);
|
||||
});
|
||||
|
||||
it('does not have change session button on regular course', () => {
|
||||
test('does not have change session button on regular course', () => {
|
||||
const wrapper = createWrapper({ isEntitlement: false });
|
||||
const sessionButton = screen.queryByRole('button', { name: defaultHooks.changeOrLeaveSessionMessage });
|
||||
expect(sessionButton).toBeNull();
|
||||
|
||||
const accessMessage = screen.getByText((text) => text.includes(defaultHooks.accessMessage));
|
||||
expect(accessMessage).toBeInTheDocument();
|
||||
expect(wrapper.container).toMatchSnapshot();
|
||||
// it has 2 separator, 3 column
|
||||
expect(fetchSeparators(wrapper)).toBe(2);
|
||||
});
|
||||
|
||||
@@ -1,68 +1,61 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { formatMessage } from 'testUtils';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import useActionDisabledState from './hooks';
|
||||
import { CourseCardImage } from './CourseCardImage';
|
||||
import messages from '../messages';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
const homeUrl = 'https://example.com';
|
||||
const bannerImgSrc = 'banner-img-src.jpg';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import track from 'tracking';
|
||||
import useActionDisabledState from './hooks';
|
||||
import CourseCardImage from './CourseCardImage';
|
||||
|
||||
const homeUrl = 'home-url';
|
||||
|
||||
jest.mock('tracking', () => ({
|
||||
course: {
|
||||
courseImageClicked: jest.fn().mockName('segment.courseImageClicked'),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCourseData: jest.fn(() => ({ bannerImgSrc })),
|
||||
useCardCourseData: jest.fn(() => ({ bannerImgSrc: 'banner-img-src' })),
|
||||
useCardCourseRunData: jest.fn(() => ({ homeUrl })),
|
||||
useCardEnrollmentData: jest.fn(),
|
||||
useCardEnrollmentData: jest.fn(() => ({ isVerified: true })),
|
||||
useTrackCourseEvent: jest.fn((eventName, cardId, url) => ({
|
||||
trackCourseEvent: { eventName, cardId, url },
|
||||
})),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('./hooks', () => jest.fn());
|
||||
jest.mock('./hooks', () => jest.fn(() => ({ disableCourseTitle: false })));
|
||||
|
||||
describe('CourseCardImage', () => {
|
||||
const props = {
|
||||
cardId: 'test-card-id',
|
||||
orientation: 'horizontal',
|
||||
cardId: 'cardId',
|
||||
orientation: 'orientation',
|
||||
};
|
||||
|
||||
it('renders course image with correct attributes', () => {
|
||||
useActionDisabledState.mockReturnValue({ disableCourseTitle: true });
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValue({ isVerified: true });
|
||||
render(<IntlProvider locale="en"><CourseCardImage {...props} /></IntlProvider>);
|
||||
|
||||
const image = screen.getByRole('img', { name: formatMessage(messages.bannerAlt) });
|
||||
expect(image).toBeInTheDocument();
|
||||
expect(image.src).toContain(bannerImgSrc);
|
||||
expect(image.parentElement).toHaveClass('horizontal');
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('isVerified, should render badge', () => {
|
||||
useActionDisabledState.mockReturnValue({ disableCourseTitle: false });
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValue({ isVerified: true });
|
||||
render(<IntlProvider locale="en"><CourseCardImage {...props} /></IntlProvider>);
|
||||
|
||||
const badge = screen.getByText(formatMessage(messages.verifiedBanner));
|
||||
expect(badge).toBeInTheDocument();
|
||||
const badgeImg = screen.getByRole('img', { name: formatMessage(messages.verifiedBannerRibbonAlt) });
|
||||
expect(badgeImg).toBeInTheDocument();
|
||||
describe('snapshot', () => {
|
||||
test('renders clickable link course Image', () => {
|
||||
const wrapper = shallow(<CourseCardImage {...props} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
expect(wrapper.instance.type).toBe('a');
|
||||
expect(wrapper.instance.props.onClick).toEqual(
|
||||
reduxHooks.useTrackCourseEvent(
|
||||
track.course.courseImageClicked,
|
||||
props.cardId,
|
||||
homeUrl,
|
||||
),
|
||||
);
|
||||
});
|
||||
test('renders disabled link', () => {
|
||||
useActionDisabledState.mockReturnValueOnce({ disableCourseTitle: true });
|
||||
const wrapper = shallow(<CourseCardImage {...props} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
expect(wrapper.instance.type).toBe('div');
|
||||
});
|
||||
});
|
||||
|
||||
it('renders link with correct href if disableCourseTitle is false', () => {
|
||||
useActionDisabledState.mockReturnValue({ disableCourseTitle: false });
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValue({ isVerified: false });
|
||||
render(<IntlProvider locale="en"><CourseCardImage {...props} /></IntlProvider>);
|
||||
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', homeUrl);
|
||||
});
|
||||
describe('hooks', () => {
|
||||
describe('behavior', () => {
|
||||
it('initializes', () => {
|
||||
useActionDisabledState.mockReturnValue({ disableCourseTitle: false });
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValue({ isVerified: true });
|
||||
render(<IntlProvider locale="en"><CourseCardImage {...props} /></IntlProvider>);
|
||||
shallow(<CourseCardImage {...props} />);
|
||||
expect(reduxHooks.useCardCourseData).toHaveBeenCalledWith(props.cardId);
|
||||
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(
|
||||
props.cardId,
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as ReactShare from 'react-share';
|
||||
|
||||
import { StrictDict } from '@edx/react-unit-test-utils';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Dropdown } from '@openedx/paragon';
|
||||
|
||||
@@ -10,9 +11,9 @@ import { reduxHooks } from 'hooks';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
export const testIds = {
|
||||
export const testIds = StrictDict({
|
||||
emailSettingsModalToggle: 'emailSettingsModalToggle',
|
||||
};
|
||||
});
|
||||
|
||||
export const SocialShareMenu = ({ cardId, emailSettings }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
@@ -1,19 +1,32 @@
|
||||
import { when } from 'jest-when';
|
||||
import * as ReactShare from 'react-share';
|
||||
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { formatMessage, shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import track from 'tracking';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import { useEmailSettings } from './hooks';
|
||||
import SocialShareMenu from './SocialShareMenu';
|
||||
import SocialShareMenu, { testIds } from './SocialShareMenu';
|
||||
import messages from './messages';
|
||||
|
||||
jest.mock('react-share', () => ({
|
||||
FacebookShareButton: () => 'FacebookShareButton',
|
||||
TwitterShareButton: () => 'TwitterShareButton',
|
||||
}));
|
||||
|
||||
jest.mock('tracking', () => ({
|
||||
socialShare: 'test-social-share-key',
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-platform/i18n', () => ({
|
||||
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||
useIntl: jest.fn().mockReturnValue({
|
||||
formatMessage: jest.requireActual('@edx/react-unit-test-utils').formatMessage,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useMasqueradeData: jest.fn(),
|
||||
@@ -34,9 +47,9 @@ const props = {
|
||||
|
||||
const mockHook = (fn, returnValue, options = {}) => {
|
||||
if (options.isCardHook) {
|
||||
when(fn).calledWith(props.cardId).mockReturnValue(returnValue);
|
||||
when(fn).calledWith(props.cardId).mockReturnValueOnce(returnValue);
|
||||
} else {
|
||||
when(fn).calledWith().mockReturnValue(returnValue);
|
||||
when(fn).calledWith().mockReturnValueOnce(returnValue);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -76,13 +89,19 @@ const mockHooks = (returnVals = {}) => {
|
||||
);
|
||||
};
|
||||
|
||||
const renderComponent = () => render(<IntlProvider locale="en"><SocialShareMenu {...props} /></IntlProvider>);
|
||||
let el;
|
||||
const render = () => {
|
||||
el = shallow(<SocialShareMenu {...props} />);
|
||||
};
|
||||
|
||||
describe('SocialShareMenu', () => {
|
||||
describe('behavior', () => {
|
||||
beforeEach(() => {
|
||||
mockHooks();
|
||||
renderComponent();
|
||||
render();
|
||||
});
|
||||
it('initializes intl hook', () => {
|
||||
expect(useIntl).toHaveBeenCalledWith();
|
||||
});
|
||||
it('initializes local hooks', () => {
|
||||
when(useEmailSettings).expectCalledWith();
|
||||
@@ -99,43 +118,53 @@ describe('SocialShareMenu', () => {
|
||||
describe('render', () => {
|
||||
it('renders null if exec ed course', () => {
|
||||
mockHooks({ isExecEd2UCourse: true });
|
||||
renderComponent();
|
||||
const emailSettingsButton = screen.queryByRole('button', { name: messages.emailSettings.defaultMessage });
|
||||
expect(emailSettingsButton).toBeNull();
|
||||
const facebookShareButton = screen.queryByRole('button', { name: 'facebook' });
|
||||
expect(facebookShareButton).toBeNull();
|
||||
const twitterShareButton = screen.queryByRole('button', { name: 'twitter' });
|
||||
expect(twitterShareButton).toBeNull();
|
||||
render();
|
||||
expect(el.isEmptyRender()).toEqual(true);
|
||||
});
|
||||
const testEmailSettingsDropdown = (isMasquerading = false) => {
|
||||
describe('email settings dropdown', () => {
|
||||
const loadToggle = () => el.instance.findByTestId(testIds.emailSettingsModalToggle)[0];
|
||||
it('renders', () => {
|
||||
const emailSettingsButton = screen.getByRole('button', { name: messages.emailSettings.defaultMessage });
|
||||
expect(emailSettingsButton).toBeInTheDocument();
|
||||
expect(el.instance.findByTestId(testIds.emailSettingsModalToggle).length).toEqual(1);
|
||||
});
|
||||
if (isMasquerading) {
|
||||
it('is disabled', () => {
|
||||
const emailSettingsButton = screen.getByRole('button', { name: messages.emailSettings.defaultMessage });
|
||||
expect(emailSettingsButton).toHaveAttribute('aria-disabled', 'true');
|
||||
expect(loadToggle().props.disabled).toEqual(true);
|
||||
});
|
||||
} else {
|
||||
it('is enabled', () => {
|
||||
const emailSettingsButton = screen.getByRole('button', { name: messages.emailSettings.defaultMessage });
|
||||
expect(emailSettingsButton).toBeEnabled();
|
||||
expect(loadToggle().props.disabled).toEqual(false);
|
||||
});
|
||||
}
|
||||
test('show email settings modal on click', () => {
|
||||
expect(loadToggle().props.onClick).toEqual(props.emailSettings.show);
|
||||
});
|
||||
});
|
||||
};
|
||||
const testFacebookShareButton = () => {
|
||||
it('renders facebook share button', () => {
|
||||
const facebookShareButton = screen.getByRole('button', { name: 'facebook' });
|
||||
expect(facebookShareButton).toBeInTheDocument();
|
||||
test('renders facebook share button with courseName and brand', () => {
|
||||
const button = el.instance.findByType(ReactShare.FacebookShareButton)[0];
|
||||
expect(button.props.url).toEqual(socialShare.facebook.shareUrl);
|
||||
expect(button.props.onClick).toEqual(
|
||||
reduxHooks.useTrackCourseEvent(track.socialShare, props.cardId, 'facebook'),
|
||||
);
|
||||
expect(button.props.title).toEqual(formatMessage(messages.shareQuote, {
|
||||
courseName,
|
||||
socialBrand: socialShare.facebook.socialBrand,
|
||||
}));
|
||||
});
|
||||
};
|
||||
const testTwitterShareButton = () => {
|
||||
it('renders twitter share button', () => {
|
||||
const twitterShareButton = screen.getByRole('button', { name: 'twitter' });
|
||||
expect(twitterShareButton).toBeInTheDocument();
|
||||
test('renders twitter share button with courseName and brand', () => {
|
||||
const button = el.instance.findByType(ReactShare.TwitterShareButton)[0];
|
||||
expect(button.props.url).toEqual(socialShare.twitter.shareUrl);
|
||||
expect(button.props.onClick).toEqual(
|
||||
reduxHooks.useTrackCourseEvent(track.socialShare, props.cardId, 'twitter'),
|
||||
);
|
||||
expect(button.props.title).toEqual(formatMessage(messages.shareQuote, {
|
||||
courseName,
|
||||
socialBrand: socialShare.twitter.socialBrand,
|
||||
}));
|
||||
});
|
||||
};
|
||||
describe('all enabled', () => {
|
||||
@@ -145,7 +174,19 @@ describe('SocialShareMenu', () => {
|
||||
twitter: { isEnabled: true },
|
||||
isEmailEnabled: true,
|
||||
});
|
||||
renderComponent();
|
||||
render();
|
||||
});
|
||||
describe('email settings dropdown', () => {
|
||||
const loadToggle = () => el.instance.findByTestId(testIds.emailSettingsModalToggle)[0];
|
||||
it('renders', () => {
|
||||
expect(el.instance.findByTestId(testIds.emailSettingsModalToggle).length).toEqual(1);
|
||||
});
|
||||
it('is enabled', () => {
|
||||
expect(loadToggle().props.disabled).toEqual(false);
|
||||
});
|
||||
test('show email settings modal on click', () => {
|
||||
expect(loadToggle().props.onClick).toEqual(props.emailSettings.show);
|
||||
});
|
||||
});
|
||||
testEmailSettingsDropdown();
|
||||
testFacebookShareButton();
|
||||
@@ -153,49 +194,42 @@ describe('SocialShareMenu', () => {
|
||||
});
|
||||
describe('only email enabled', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockHooks({ isEmailEnabled: true });
|
||||
renderComponent();
|
||||
render();
|
||||
});
|
||||
testEmailSettingsDropdown();
|
||||
it('does not render facebook or twitter controls', () => {
|
||||
const facebookShareButton = screen.queryByRole('button', { name: 'facebook' });
|
||||
expect(facebookShareButton).toBeNull();
|
||||
const twitterShareButton = screen.queryByRole('button', { name: 'twitter' });
|
||||
expect(twitterShareButton).toBeNull();
|
||||
expect(el.instance.findByType(ReactShare.FacebookShareButton).length).toEqual(0);
|
||||
expect(el.instance.findByType(ReactShare.TwitterShareButton).length).toEqual(0);
|
||||
});
|
||||
});
|
||||
describe('masquerading', () => {
|
||||
beforeEach(() => {
|
||||
mockHooks({ isEmailEnabled: true, isMasquerading: true });
|
||||
renderComponent();
|
||||
describe('masquerading', () => {
|
||||
beforeEach(() => {
|
||||
mockHooks({ isEmailEnabled: true, isMasquerading: true });
|
||||
render();
|
||||
});
|
||||
testEmailSettingsDropdown(true);
|
||||
});
|
||||
testEmailSettingsDropdown(true);
|
||||
});
|
||||
describe('only facebook enabled', () => {
|
||||
beforeEach(() => {
|
||||
mockHooks({ facebook: { isEnabled: true } });
|
||||
renderComponent();
|
||||
render();
|
||||
});
|
||||
testFacebookShareButton();
|
||||
it('does not render email or twitter controls', () => {
|
||||
const emailSettingsButton = screen.queryByRole('button', { name: messages.emailSettings.defaultMessage });
|
||||
expect(emailSettingsButton).toBeNull();
|
||||
const twitterShareButton = screen.queryByRole('button', { name: 'twitter' });
|
||||
expect(twitterShareButton).toBeNull();
|
||||
expect(el.instance.findByTestId(testIds.emailSettingsModalToggle).length).toEqual(0);
|
||||
expect(el.instance.findByType(ReactShare.TwitterShareButton).length).toEqual(0);
|
||||
});
|
||||
});
|
||||
describe('only twitter enabled', () => {
|
||||
beforeEach(() => {
|
||||
mockHooks({ twitter: { isEnabled: true } });
|
||||
renderComponent();
|
||||
render();
|
||||
});
|
||||
testTwitterShareButton();
|
||||
it('does not render email or facebook controls', () => {
|
||||
const emailSettingsButton = screen.queryByRole('button', { name: messages.emailSettings.defaultMessage });
|
||||
expect(emailSettingsButton).toBeNull();
|
||||
const facebookShareButton = screen.queryByRole('button', { name: 'facebook' });
|
||||
expect(facebookShareButton).toBeNull();
|
||||
expect(el.instance.findByTestId(testIds.emailSettingsModalToggle).length).toEqual(0);
|
||||
expect(el.instance.findByType(ReactShare.FacebookShareButton).length).toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CourseCardMenu render show dropdown hide unenroll item and disable email snapshot 1`] = `
|
||||
<Fragment>
|
||||
<Dropdown
|
||||
onToggle={[MockFunction hooks.handleToggleDropdown]}
|
||||
>
|
||||
<Dropdown.Toggle
|
||||
alt="Course actions dropdown"
|
||||
as="IconButton"
|
||||
iconAs="Icon"
|
||||
id="course-actions-dropdown-test-card-id"
|
||||
src={[MockFunction icons.MoreVert]}
|
||||
variant="primary"
|
||||
/>
|
||||
<Dropdown.Menu>
|
||||
<SocialShareMenu
|
||||
cardId="test-card-id"
|
||||
emailSettings={
|
||||
{
|
||||
"hide": [MockFunction emailSettingHide],
|
||||
"isVisible": false,
|
||||
"show": [MockFunction emailSettingShow],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
<UnenrollConfirmModal
|
||||
cardId="test-card-id"
|
||||
closeModal={[MockFunction unenrollHide]}
|
||||
show={false}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`CourseCardMenu render show dropdown show unenroll and enable email snapshot 1`] = `
|
||||
<Fragment>
|
||||
<Dropdown
|
||||
onToggle={[MockFunction hooks.handleToggleDropdown]}
|
||||
>
|
||||
<Dropdown.Toggle
|
||||
alt="Course actions dropdown"
|
||||
as="IconButton"
|
||||
iconAs="Icon"
|
||||
id="course-actions-dropdown-test-card-id"
|
||||
src={[MockFunction icons.MoreVert]}
|
||||
variant="primary"
|
||||
/>
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item
|
||||
data-testid="unenrollModalToggle"
|
||||
disabled={false}
|
||||
onClick={[MockFunction unenrollShow]}
|
||||
>
|
||||
Unenroll
|
||||
</Dropdown.Item>
|
||||
<SocialShareMenu
|
||||
cardId="test-card-id"
|
||||
emailSettings={
|
||||
{
|
||||
"hide": [MockFunction emailSettingHide],
|
||||
"isVisible": false,
|
||||
"show": [MockFunction emailSettingShow],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
<UnenrollConfirmModal
|
||||
cardId="test-card-id"
|
||||
closeModal={[MockFunction unenrollHide]}
|
||||
show={false}
|
||||
/>
|
||||
<EmailSettingsModal
|
||||
cardId="test-card-id"
|
||||
closeModal={[MockFunction emailSettingHide]}
|
||||
show={false}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
@@ -1,15 +1,15 @@
|
||||
import { useKeyedState, StrictDict } from '@edx/react-unit-test-utils';
|
||||
|
||||
import track from 'tracking';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { useState } from 'react';
|
||||
import { StrictDict } from 'utils';
|
||||
|
||||
export const state = StrictDict({
|
||||
isUnenrollConfirmVisible: (val) => useState(val), // eslint-disable-line
|
||||
isEmailSettingsVisible: (val) => useState(val), // eslint-disable-line
|
||||
export const stateKeys = StrictDict({
|
||||
isUnenrollConfirmVisible: 'isUnenrollConfirmVisible',
|
||||
isEmailSettingsVisible: 'isEmailSettingsVisible',
|
||||
});
|
||||
|
||||
export const useUnenrollData = () => {
|
||||
const [isVisible, setIsVisible] = state.isUnenrollConfirmVisible(false);
|
||||
const [isVisible, setIsVisible] = useKeyedState(stateKeys.isUnenrollConfirmVisible, false);
|
||||
return {
|
||||
show: () => setIsVisible(true),
|
||||
hide: () => setIsVisible(false),
|
||||
@@ -18,7 +18,7 @@ export const useUnenrollData = () => {
|
||||
};
|
||||
|
||||
export const useEmailSettings = () => {
|
||||
const [isVisible, setIsVisible] = state.isEmailSettingsVisible(false);
|
||||
const [isVisible, setIsVisible] = useKeyedState(stateKeys.isEmailSettingsVisible, false);
|
||||
return {
|
||||
show: () => setIsVisible(true),
|
||||
hide: () => setIsVisible(false),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { mockUseKeyedState } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import track from 'tracking';
|
||||
import { MockUseState } from 'testUtils';
|
||||
|
||||
import * as hooks from './hooks';
|
||||
|
||||
@@ -18,7 +19,7 @@ reduxHooks.useTrackCourseEvent.mockReturnValue(trackCourseEvent);
|
||||
const cardId = 'test-card-id';
|
||||
let out;
|
||||
|
||||
const state = new MockUseState(hooks);
|
||||
const state = mockUseKeyedState(hooks.stateKeys);
|
||||
|
||||
describe('CourseCardMenu hooks', () => {
|
||||
beforeEach(() => {
|
||||
@@ -27,6 +28,7 @@ describe('CourseCardMenu hooks', () => {
|
||||
});
|
||||
describe('useUnenrollData', () => {
|
||||
beforeEach(() => {
|
||||
state.mockVals({ isUnenrollConfirmVisible: true });
|
||||
out = hooks.useUnenrollData();
|
||||
});
|
||||
describe('behavior', () => {
|
||||
@@ -35,6 +37,9 @@ describe('CourseCardMenu hooks', () => {
|
||||
});
|
||||
});
|
||||
describe('output', () => {
|
||||
test('state is loaded from current state value', () => {
|
||||
expect(out.isVisible).toEqual(true);
|
||||
});
|
||||
test('show sets state value to true', () => {
|
||||
out.show();
|
||||
expect(state.setState.isUnenrollConfirmVisible).toHaveBeenCalledWith(true);
|
||||
@@ -48,6 +53,7 @@ describe('CourseCardMenu hooks', () => {
|
||||
|
||||
describe('useEmailSettings', () => {
|
||||
beforeEach(() => {
|
||||
state.mockVals({ isEmailSettingsVisible: true });
|
||||
out = hooks.useEmailSettings();
|
||||
});
|
||||
describe('behavior', () => {
|
||||
@@ -56,6 +62,9 @@ describe('CourseCardMenu hooks', () => {
|
||||
});
|
||||
});
|
||||
describe('output', () => {
|
||||
test('state is loaded from current state value', () => {
|
||||
expect(out.isVisible).toEqual(state.values.isEmailSettingsVisible);
|
||||
});
|
||||
test('show sets state value to true', () => {
|
||||
out.show();
|
||||
expect(state.setState.isEmailSettingsVisible).toHaveBeenCalledWith(true);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Dropdown, Icon, IconButton } from '@openedx/paragon';
|
||||
import { MoreVert } from '@openedx/paragon/icons';
|
||||
import { StrictDict } from '@edx/react-unit-test-utils';
|
||||
|
||||
import EmailSettingsModal from 'containers/EmailSettingsModal';
|
||||
import UnenrollConfirmModal from 'containers/UnenrollConfirmModal';
|
||||
@@ -17,9 +19,9 @@ import {
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
export const testIds = {
|
||||
export const testIds = StrictDict({
|
||||
unenrollModalToggle: 'unenrollModalToggle',
|
||||
};
|
||||
});
|
||||
|
||||
export const CourseCardMenu = ({ cardId }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
import { when } from 'jest-when';
|
||||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { Dropdown } from '@openedx/paragon';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import EmailSettingsModal from 'containers/EmailSettingsModal';
|
||||
import UnenrollConfirmModal from 'containers/UnenrollConfirmModal';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import SocialShareMenu from './SocialShareMenu';
|
||||
import * as hooks from './hooks';
|
||||
import CourseCardMenu from '.';
|
||||
import messages from './messages';
|
||||
import CourseCardMenu, { testIds } from '.';
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useMasqueradeData: jest.fn(),
|
||||
useCardEnrollmentData: jest.fn(),
|
||||
},
|
||||
jest.mock('@edx/frontend-platform/i18n', () => ({
|
||||
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||
useIntl: jest.fn().mockReturnValue({
|
||||
formatMessage: jest.requireActual('@edx/react-unit-test-utils').formatMessage,
|
||||
}),
|
||||
}));
|
||||
jest.mock('./SocialShareMenu', () => jest.fn(() => <div>SocialShareMenu</div>));
|
||||
jest.mock('containers/EmailSettingsModal', () => jest.fn(() => <div>EmailSettingsModal</div>));
|
||||
jest.mock('containers/UnenrollConfirmModal', () => jest.fn(() => <div>UnenrollConfirmModal</div>));
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: { useMasqueradeData: jest.fn(), useCardEnrollmentData: jest.fn() },
|
||||
}));
|
||||
jest.mock('./SocialShareMenu', () => 'SocialShareMenu');
|
||||
jest.mock('./hooks', () => ({
|
||||
useEmailSettings: jest.fn(),
|
||||
useUnenrollData: jest.fn(),
|
||||
@@ -41,11 +44,13 @@ const unenrollData = {
|
||||
hide: jest.fn().mockName('unenrollHide'),
|
||||
};
|
||||
|
||||
let el;
|
||||
|
||||
const mockHook = (fn, returnValue, options = {}) => {
|
||||
if (options.isCardHook) {
|
||||
when(fn).calledWith(props.cardId).mockReturnValue(returnValue);
|
||||
when(fn).calledWith(props.cardId).mockReturnValueOnce(returnValue);
|
||||
} else {
|
||||
when(fn).calledWith().mockReturnValue(returnValue);
|
||||
when(fn).calledWith().mockReturnValueOnce(returnValue);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -77,13 +82,18 @@ const mockHooks = (returnVals = {}) => {
|
||||
);
|
||||
};
|
||||
|
||||
const renderComponent = () => render(<IntlProvider locale="en"><CourseCardMenu {...props} /></IntlProvider>);
|
||||
const render = () => {
|
||||
el = shallow(<CourseCardMenu {...props} />);
|
||||
};
|
||||
|
||||
describe('CourseCardMenu', () => {
|
||||
describe('hooks', () => {
|
||||
describe('behavior', () => {
|
||||
beforeEach(() => {
|
||||
mockHooks();
|
||||
renderComponent();
|
||||
render();
|
||||
});
|
||||
it('initializes intl hook', () => {
|
||||
expect(useIntl).toHaveBeenCalledWith();
|
||||
});
|
||||
it('initializes local hooks', () => {
|
||||
when(hooks.useEmailSettings).expectCalledWith();
|
||||
@@ -99,30 +109,47 @@ describe('CourseCardMenu', () => {
|
||||
describe('render', () => {
|
||||
it('renders null if showDropdown is false', () => {
|
||||
mockHooks();
|
||||
renderComponent();
|
||||
const dropdown = screen.queryByRole('button', { name: messages.dropdownAlt.defaultMessage });
|
||||
expect(dropdown).toBeNull();
|
||||
render();
|
||||
expect(el.isEmptyRender()).toEqual(true);
|
||||
});
|
||||
const testHandleToggle = () => {
|
||||
it('displays Dropdown with onToggle=handleToggleDropdown', () => {
|
||||
expect(el.instance.findByType(Dropdown)[0].props.onToggle).toEqual(handleToggleDropdown);
|
||||
});
|
||||
};
|
||||
const testUnenrollConfirmModal = () => {
|
||||
it('displays UnenrollConfirmModal with cardId and unenrollModal data', () => {
|
||||
const modal = el.instance.findByType(UnenrollConfirmModal)[0];
|
||||
expect(modal.props.show).toEqual(unenrollData.isVisible);
|
||||
expect(modal.props.closeModal).toEqual(unenrollData.hide);
|
||||
expect(modal.props.cardId).toEqual(props.cardId);
|
||||
});
|
||||
};
|
||||
const testSocialShareMenu = () => {
|
||||
it('displays SocialShareMenu with cardID and emailSettings', () => {
|
||||
const menu = el.instance.findByType(SocialShareMenu)[0];
|
||||
expect(menu.props.cardId).toEqual(props.cardId);
|
||||
expect(menu.props.emailSettings).toEqual(emailSettings);
|
||||
});
|
||||
};
|
||||
describe('show dropdown', () => {
|
||||
describe('hide unenroll item and disable email', () => {
|
||||
it('displays Dropdown and renders SocialShareMenu and UnenrollConfirmModal', async () => {
|
||||
mockHooks({
|
||||
shouldShowDropdown: true,
|
||||
});
|
||||
renderComponent();
|
||||
const user = userEvent.setup();
|
||||
const dropdown = screen.getByRole('button', { name: messages.dropdownAlt.defaultMessage });
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
await user.click(dropdown);
|
||||
const unenrollOption = screen.queryByRole('button', { name: messages.unenroll.defaultMessage });
|
||||
expect(unenrollOption).toBeNull();
|
||||
const socialShareMenu = screen.getByText('SocialShareMenu');
|
||||
expect(socialShareMenu).toBeInTheDocument();
|
||||
const unenrollConfirmModal = screen.getByText('UnenrollConfirmModal');
|
||||
expect(unenrollConfirmModal).toBeInTheDocument();
|
||||
const emailSettingsModal = screen.queryByText('EmailSettingsModal');
|
||||
expect(emailSettingsModal).toBeNull();
|
||||
beforeEach(() => {
|
||||
mockHooks({ shouldShowDropdown: true });
|
||||
render();
|
||||
});
|
||||
test('snapshot', () => {
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
testHandleToggle();
|
||||
testSocialShareMenu();
|
||||
it('does not render unenroll modal toggle', () => {
|
||||
expect(el.instance.findByTestId(testIds.unenrollModalToggle).length).toEqual(0);
|
||||
});
|
||||
it('does not render EmailSettingsModal', () => {
|
||||
expect(el.instance.findByType(EmailSettingsModal).length).toEqual(0);
|
||||
});
|
||||
testUnenrollConfirmModal();
|
||||
});
|
||||
describe('show unenroll and enable email', () => {
|
||||
const hookProps = {
|
||||
@@ -130,49 +157,57 @@ describe('CourseCardMenu', () => {
|
||||
isEmailEnabled: true,
|
||||
shouldShowUnenrollItem: true,
|
||||
};
|
||||
beforeEach(() => {
|
||||
mockHooks(hookProps);
|
||||
render();
|
||||
});
|
||||
test('snapshot', () => {
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
testHandleToggle();
|
||||
testSocialShareMenu();
|
||||
describe('unenroll modal toggle', () => {
|
||||
let toggle;
|
||||
describe('not masquerading', () => {
|
||||
it('renders all components', async () => {
|
||||
beforeEach(() => {
|
||||
mockHooks(hookProps);
|
||||
renderComponent();
|
||||
|
||||
const user = userEvent.setup();
|
||||
const dropdown = screen.getByRole('button', { name: messages.dropdownAlt.defaultMessage });
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
await user.click(dropdown);
|
||||
|
||||
const unenrollOption = screen.getByRole('button', { name: messages.unenroll.defaultMessage });
|
||||
expect(unenrollOption).toBeInTheDocument();
|
||||
const socialShareMenu = screen.getByText('SocialShareMenu');
|
||||
expect(socialShareMenu).toBeInTheDocument();
|
||||
const unenrollConfirmModal = screen.getByText('UnenrollConfirmModal');
|
||||
expect(unenrollConfirmModal).toBeInTheDocument();
|
||||
const emailSettingsModal = screen.getByText('EmailSettingsModal');
|
||||
expect(emailSettingsModal).toBeInTheDocument();
|
||||
render();
|
||||
[toggle] = el.instance.findByTestId(testIds.unenrollModalToggle);
|
||||
});
|
||||
it('renders unenroll modal toggle', () => {
|
||||
expect(el.instance.findByTestId(testIds.unenrollModalToggle).length).toEqual(1);
|
||||
});
|
||||
test('onClick from unenroll modal hook', () => {
|
||||
expect(toggle.props.onClick).toEqual(unenrollData.show);
|
||||
});
|
||||
test('disabled', () => {
|
||||
expect(toggle.props.disabled).toEqual(false);
|
||||
});
|
||||
});
|
||||
describe('masquerading', () => {
|
||||
it('renders but unenroll is disabled', async () => {
|
||||
beforeEach(() => {
|
||||
mockHooks({ ...hookProps, isMasquerading: true });
|
||||
renderComponent();
|
||||
|
||||
const user = userEvent.setup();
|
||||
const dropdown = screen.getByRole('button', { name: messages.dropdownAlt.defaultMessage });
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
await user.click(dropdown);
|
||||
|
||||
const unenrollOption = screen.getByRole('button', { name: messages.unenroll.defaultMessage });
|
||||
expect(unenrollOption).toBeInTheDocument();
|
||||
expect(unenrollOption).toHaveAttribute('aria-disabled', 'true');
|
||||
const socialShareMenu = screen.getByText('SocialShareMenu');
|
||||
expect(socialShareMenu).toBeInTheDocument();
|
||||
const unenrollConfirmModal = screen.getByText('UnenrollConfirmModal');
|
||||
expect(unenrollConfirmModal).toBeInTheDocument();
|
||||
const emailSettingsModal = screen.getByText('EmailSettingsModal');
|
||||
expect(emailSettingsModal).toBeInTheDocument();
|
||||
render();
|
||||
[toggle] = el.instance.findByTestId(testIds.unenrollModalToggle);
|
||||
});
|
||||
it('renders', () => {
|
||||
expect(el.instance.findByTestId(testIds.unenrollModalToggle).length).toEqual(1);
|
||||
});
|
||||
test('onClick from unenroll modal hook', () => {
|
||||
expect(toggle.props.onClick).toEqual(unenrollData.show);
|
||||
});
|
||||
test('disabled', () => {
|
||||
expect(toggle.props.disabled).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
testUnenrollConfirmModal();
|
||||
it('displays EmaiSettingsModal with cardId and emailSettingsModal data', () => {
|
||||
const modal = el.instance.findByType(EmailSettingsModal)[0];
|
||||
expect(modal.props.show).toEqual(emailSettings.isVisible);
|
||||
expect(modal.props.closeModal).toEqual(emailSettings.hide);
|
||||
expect(modal.props.cardId).toEqual(props.cardId);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import track from 'tracking';
|
||||
import useActionDisabledState from './hooks';
|
||||
import CourseCardTitle from './CourseCardTitle';
|
||||
|
||||
const homeUrl = 'home-url';
|
||||
|
||||
jest.mock('tracking', () => ({
|
||||
course: {
|
||||
courseTitleClicked: jest.fn().mockName('segment.courseTitleClicked'),
|
||||
@@ -13,61 +15,53 @@ jest.mock('tracking', () => ({
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCourseData: jest.fn(),
|
||||
useCardCourseRunData: jest.fn(),
|
||||
useTrackCourseEvent: jest.fn(),
|
||||
useCardCourseData: jest.fn(() => ({ courseName: 'course-name' })),
|
||||
useCardCourseRunData: jest.fn(() => ({ homeUrl })),
|
||||
useTrackCourseEvent: jest.fn((eventName, cardId, url) => ({
|
||||
trackCourseEvent: { eventName, cardId, url },
|
||||
})),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('./hooks', () => jest.fn(() => ({ disableCourseTitle: false })));
|
||||
|
||||
describe('CourseCardTitle', () => {
|
||||
const props = {
|
||||
cardId: 'test-card-id',
|
||||
cardId: 'cardId',
|
||||
};
|
||||
|
||||
const courseName = 'Test Course';
|
||||
const homeUrl = 'http://test.com';
|
||||
const handleTitleClick = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
reduxHooks.useCardCourseData.mockReturnValue({ courseName });
|
||||
reduxHooks.useCardCourseRunData.mockReturnValue({ homeUrl });
|
||||
reduxHooks.useTrackCourseEvent.mockReturnValue(handleTitleClick);
|
||||
});
|
||||
|
||||
it('renders course name as link when not disabled', async () => {
|
||||
useActionDisabledState.mockReturnValue({ disableCourseTitle: false });
|
||||
render(<CourseCardTitle {...props} />);
|
||||
|
||||
const user = userEvent.setup();
|
||||
const link = screen.getByRole('link', { name: courseName });
|
||||
expect(link).toHaveAttribute('href', homeUrl);
|
||||
|
||||
await user.click(link);
|
||||
expect(handleTitleClick).toHaveBeenCalled();
|
||||
describe('snapshot', () => {
|
||||
test('renders clickable link course title', () => {
|
||||
const wrapper = shallow(<CourseCardTitle {...props} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
const title = wrapper.instance.findByTestId('CourseCardTitle');
|
||||
expect(title[0].type).toBe('a');
|
||||
expect(title[0].props.onClick).toEqual(
|
||||
reduxHooks.useTrackCourseEvent(
|
||||
track.course.courseTitleClicked,
|
||||
props.cardId,
|
||||
homeUrl,
|
||||
),
|
||||
);
|
||||
});
|
||||
test('renders disabled link', () => {
|
||||
useActionDisabledState.mockReturnValueOnce({ disableCourseTitle: true });
|
||||
const wrapper = shallow(<CourseCardTitle {...props} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
const title = wrapper.instance.findByTestId('CourseCardTitle');
|
||||
expect(title[0].type).toBe('span');
|
||||
expect(title[0].props.onClick).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders course name as span when disabled', () => {
|
||||
useActionDisabledState.mockReturnValue({ disableCourseTitle: true });
|
||||
render(<CourseCardTitle {...props} />);
|
||||
|
||||
const text = screen.getByText(courseName);
|
||||
expect(text).toBeInTheDocument();
|
||||
expect(text.tagName.toLowerCase()).toBe('span');
|
||||
});
|
||||
|
||||
it('uses correct hooks with cardId', () => {
|
||||
useActionDisabledState.mockReturnValue({ disableCourseTitle: false });
|
||||
render(<CourseCardTitle {...props} />);
|
||||
|
||||
expect(reduxHooks.useCardCourseData).toHaveBeenCalledWith(props.cardId);
|
||||
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(props.cardId);
|
||||
expect(reduxHooks.useTrackCourseEvent).toHaveBeenCalledWith(
|
||||
track.course.courseTitleClicked,
|
||||
props.cardId,
|
||||
homeUrl,
|
||||
);
|
||||
describe('behavior', () => {
|
||||
it('initializes', () => {
|
||||
shallow(<CourseCardTitle {...props} />);
|
||||
expect(reduxHooks.useCardCourseData).toHaveBeenCalledWith(props.cardId);
|
||||
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(
|
||||
props.cardId,
|
||||
);
|
||||
expect(useActionDisabledState).toHaveBeenCalledWith(props.cardId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`RelatedProgramsBadge component snapshot: 3 programs 1`] = `
|
||||
<Fragment>
|
||||
<Button
|
||||
className="pl-0 mr-0 justify-content-start align-self-start flex-shrink-1"
|
||||
data-testid="RelatedProgramsBadge"
|
||||
onClick={[MockFunction useRelatedProgramsBadge.openModal]}
|
||||
size="sm"
|
||||
variant="tertiary"
|
||||
>
|
||||
<Icon
|
||||
className="mr-2 pr-0"
|
||||
src={[MockFunction icons.Program]}
|
||||
/>
|
||||
|
||||
useRelatedProgramsBadge.programsMessage
|
||||
</Button>
|
||||
<RelatedProgramsModal
|
||||
cardId="test-course-number"
|
||||
closeModal={[MockFunction useRelatedProgramsBadge.closeModal]}
|
||||
isOpen={true}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
@@ -12,16 +12,6 @@ jest.mock('hooks', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-platform/i18n', () => {
|
||||
const { formatMessage } = jest.requireActual('testUtils');
|
||||
return {
|
||||
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||
useIntl: () => ({
|
||||
formatMessage,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const cardId = 'test-card-id';
|
||||
|
||||
const state = new MockUseState(hooks);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import useRelatedProgramsBadge from './hooks';
|
||||
import RelatedProgramsBadge from '.';
|
||||
@@ -15,21 +15,16 @@ const hookProps = {
|
||||
programsMessage: 'useRelatedProgramsBadge.programsMessage',
|
||||
};
|
||||
|
||||
const cardId = 'test-card-id';
|
||||
const cardId = 'test-course-number';
|
||||
|
||||
describe('RelatedProgramsBadge component', () => {
|
||||
it('should not render if no programs', () => {
|
||||
test('empty render: no programs', () => {
|
||||
useRelatedProgramsBadge.mockReturnValueOnce({ ...hookProps, numPrograms: 0 });
|
||||
render(<IntlProvider locale="en"><RelatedProgramsBadge cardId={cardId} /></IntlProvider>);
|
||||
const button = screen.queryByRole('button', { name: hookProps.programsMessage });
|
||||
expect(button).toBeNull();
|
||||
const dialog = screen.queryByRole('dialog');
|
||||
expect(dialog).toBeNull();
|
||||
const el = shallow(<RelatedProgramsBadge cardId={cardId} />);
|
||||
expect(el.isEmptyRender()).toEqual(true);
|
||||
});
|
||||
it('3 programs closed', () => {
|
||||
useRelatedProgramsBadge.mockReturnValue({ ...hookProps, isOpen: false });
|
||||
render(<IntlProvider locale="en"><RelatedProgramsBadge cardId={cardId} /></IntlProvider>);
|
||||
const button = screen.getByRole('button', { name: hookProps.programsMessage });
|
||||
expect(button).toBeInTheDocument();
|
||||
test('snapshot: 3 programs', () => {
|
||||
useRelatedProgramsBadge.mockReturnValueOnce(hookProps);
|
||||
expect(shallow(<RelatedProgramsBadge cardId={cardId} />).snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CourseCardImage snapshot renders clickable link course Image 1`] = `
|
||||
<a
|
||||
className="pgn__card-wrapper-image-cap d-inline-block overflow-visible orientation"
|
||||
href="home-url"
|
||||
onClick={
|
||||
{
|
||||
"trackCourseEvent": {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.courseImageClicked],
|
||||
"url": "home-url",
|
||||
},
|
||||
}
|
||||
}
|
||||
tabIndex="-1"
|
||||
>
|
||||
<Fragment>
|
||||
<img
|
||||
alt="Course thumbnail"
|
||||
className="pgn__card-image-cap w-100 show"
|
||||
src="banner-img-src"
|
||||
/>
|
||||
<span
|
||||
className="course-card-verify-ribbon-container"
|
||||
title="You're enrolled as a verified student"
|
||||
>
|
||||
<Badge
|
||||
as="div"
|
||||
className="w-100"
|
||||
variant="success"
|
||||
>
|
||||
Verified
|
||||
</Badge>
|
||||
<img
|
||||
alt="ID Verified Ribbon/Badge"
|
||||
src="test-file-stub"
|
||||
/>
|
||||
</span>
|
||||
</Fragment>
|
||||
</a>
|
||||
`;
|
||||
|
||||
exports[`CourseCardImage snapshot renders disabled link 1`] = `
|
||||
<div
|
||||
className="pgn__card-wrapper-image-cap d-inline-block overflow-visible orientation"
|
||||
>
|
||||
<Fragment>
|
||||
<img
|
||||
alt="Course thumbnail"
|
||||
className="pgn__card-image-cap w-100 show"
|
||||
src="banner-img-src"
|
||||
/>
|
||||
<span
|
||||
className="course-card-verify-ribbon-container"
|
||||
title="You're enrolled as a verified student"
|
||||
>
|
||||
<Badge
|
||||
as="div"
|
||||
className="w-100"
|
||||
variant="success"
|
||||
>
|
||||
Verified
|
||||
</Badge>
|
||||
<img
|
||||
alt="ID Verified Ribbon/Badge"
|
||||
src="test-file-stub"
|
||||
/>
|
||||
</span>
|
||||
</Fragment>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,33 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CourseCardTitle snapshot renders clickable link course title 1`] = `
|
||||
<h3>
|
||||
<a
|
||||
className="course-card-title"
|
||||
data-testid="CourseCardTitle"
|
||||
href="home-url"
|
||||
onClick={
|
||||
{
|
||||
"trackCourseEvent": {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.courseTitleClicked],
|
||||
"url": "home-url",
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
course-name
|
||||
</a>
|
||||
</h3>
|
||||
`;
|
||||
|
||||
exports[`CourseCardTitle snapshot renders disabled link 1`] = `
|
||||
<h3>
|
||||
<span
|
||||
className="course-card-title"
|
||||
data-testid="CourseCardTitle"
|
||||
>
|
||||
course-name
|
||||
</span>
|
||||
</h3>
|
||||
`;
|
||||
@@ -11,16 +11,6 @@ jest.mock('hooks', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-platform/i18n', () => {
|
||||
const { formatMessage } = jest.requireActual('testUtils');
|
||||
return {
|
||||
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||
useIntl: () => ({
|
||||
formatMessage,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const cardId = 'my-test-course-number';
|
||||
|
||||
describe('CourseCard hooks', () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import CourseCard from '.';
|
||||
import hooks from './hooks';
|
||||
@@ -8,43 +8,22 @@ jest.mock('./hooks', () => ({
|
||||
useIsCollapsed: jest.fn(),
|
||||
}));
|
||||
|
||||
const namesMockComponents = [
|
||||
'CourseCardBanners',
|
||||
'CourseCardImage',
|
||||
'CourseCardMenu',
|
||||
'CourseCardActions',
|
||||
'CourseCardDetails',
|
||||
'CourseCardTitle',
|
||||
];
|
||||
|
||||
jest.mock('./components/CourseCardBanners', () => jest.fn(() => <div>CourseCardBanners</div>));
|
||||
jest.mock('./components/CourseCardImage', () => jest.fn(() => <div>CourseCardImage</div>));
|
||||
jest.mock('./components/CourseCardMenu', () => jest.fn(() => <div>CourseCardMenu</div>));
|
||||
jest.mock('./components/CourseCardActions', () => jest.fn(() => <div>CourseCardActions</div>));
|
||||
jest.mock('./components/CourseCardDetails', () => jest.fn(() => <div>CourseCardDetails</div>));
|
||||
jest.mock('./components/CourseCardTitle', () => jest.fn(() => <div>CourseCardTitle</div>));
|
||||
jest.mock('./components/CourseCardBanners', () => 'CourseCardBanners');
|
||||
jest.mock('./components/CourseCardImage', () => 'CourseCardImage');
|
||||
jest.mock('./components/CourseCardMenu', () => 'CourseCardMenu');
|
||||
jest.mock('./components/CourseCardActions', () => 'CourseCardActions');
|
||||
jest.mock('./components/CourseCardDetails', () => 'CourseCardDetails');
|
||||
jest.mock('./components/CourseCardTitle', () => 'CourseCardTitle');
|
||||
|
||||
const cardId = 'test-card-id';
|
||||
|
||||
describe('CourseCard component', () => {
|
||||
it('collapsed', () => {
|
||||
test('snapshot: collapsed', () => {
|
||||
hooks.useIsCollapsed.mockReturnValueOnce(true);
|
||||
render(<IntlProvider locale="en"><CourseCard cardId={cardId} /></IntlProvider>);
|
||||
const cardImage = screen.getByText('CourseCardImage');
|
||||
expect(cardImage.parentElement).not.toHaveClass('d-flex');
|
||||
expect(shallow(<CourseCard cardId={cardId} />).snapshot).toMatchSnapshot();
|
||||
});
|
||||
it('not collapsed', () => {
|
||||
test('snapshot: not collapsed', () => {
|
||||
hooks.useIsCollapsed.mockReturnValueOnce(false);
|
||||
render(<IntlProvider locale="en"><CourseCard cardId={cardId} /></IntlProvider>);
|
||||
const cardImage = screen.getByText('CourseCardImage');
|
||||
expect(cardImage.parentElement).toHaveClass('d-flex');
|
||||
});
|
||||
it('renders courseCard child components', () => {
|
||||
hooks.useIsCollapsed.mockReturnValueOnce(false);
|
||||
render(<IntlProvider locale="en"><CourseCard cardId={cardId} /></IntlProvider>);
|
||||
namesMockComponents.map((courseCardName) => {
|
||||
const courseCardComponent = screen.getByText(courseCardName);
|
||||
return expect(courseCardComponent).toBeInTheDocument();
|
||||
});
|
||||
expect(shallow(<CourseCard cardId={cardId} />).snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,28 +1,17 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { formatMessage } from 'testUtils';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { FilterKeys } from 'data/constants/app';
|
||||
import ActiveCourseFilters from './ActiveCourseFilters';
|
||||
import messages from './messages';
|
||||
|
||||
const filters = Object.values(FilterKeys);
|
||||
|
||||
describe('ActiveCourseFilters', () => {
|
||||
const props = {
|
||||
filters,
|
||||
filters: Object.values(FilterKeys),
|
||||
handleRemoveFilter: jest.fn().mockName('handleRemoveFilter'),
|
||||
};
|
||||
it('renders chips correctly', () => {
|
||||
render(<IntlProvider locale="en"><ActiveCourseFilters {...props} /></IntlProvider>);
|
||||
filters.map((key) => {
|
||||
const chip = screen.getByText(formatMessage(messages[key]));
|
||||
return expect(chip).toBeInTheDocument();
|
||||
describe('snapshot', () => {
|
||||
test('renders', () => {
|
||||
const wrapper = shallow(<ActiveCourseFilters {...props} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
it('renders button correctly', () => {
|
||||
render(<IntlProvider locale="en"><ActiveCourseFilters {...props} /></IntlProvider>);
|
||||
const button = screen.getByRole('button', { name: formatMessage(messages.clearAll) });
|
||||
expect(button).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { formatMessage } from 'testUtils';
|
||||
import { breakpoints, useWindowSize } from '@openedx/paragon';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { FilterKeys, SortKeys } from 'data/constants/app';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { breakpoints, useWindowSize } from '@openedx/paragon';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import messages from './messages';
|
||||
import CourseFilterControls from './CourseFilterControls';
|
||||
import useCourseFilterControlsData from './hooks';
|
||||
|
||||
@@ -13,63 +11,50 @@ jest.mock('hooks', () => ({
|
||||
reduxHooks: { useHasCourses: jest.fn() },
|
||||
}));
|
||||
|
||||
jest.mock('./hooks', () => jest.fn());
|
||||
jest.mock('./hooks', () => jest.fn().mockName('useCourseFilterControlsData'));
|
||||
|
||||
jest.mock('@openedx/paragon', () => ({
|
||||
...jest.requireActual('@openedx/paragon'),
|
||||
useWindowSize: jest.fn(),
|
||||
}));
|
||||
jest.mock('./components/FilterForm', () => 'FilterForm');
|
||||
jest.mock('./components/SortForm', () => 'SortForm');
|
||||
|
||||
const filters = Object.values(FilterKeys);
|
||||
|
||||
const mockControlsData = {
|
||||
isOpen: false,
|
||||
open: jest.fn().mockName('open'),
|
||||
close: jest.fn().mockName('close'),
|
||||
target: 'target-test',
|
||||
setTarget: jest.fn(),
|
||||
handleFilterChange: jest.fn().mockName('handleFilterChange'),
|
||||
handleSortChange: jest.fn().mockName('handleSortChange'),
|
||||
};
|
||||
reduxHooks.useHasCourses.mockReturnValue(true);
|
||||
|
||||
describe('CourseFilterControls', () => {
|
||||
const props = {
|
||||
sortBy: SortKeys.enrolled,
|
||||
sortBy: 'test-sort-by',
|
||||
setSortBy: jest.fn().mockName('setSortBy'),
|
||||
filters,
|
||||
filters: ['test-filter'],
|
||||
};
|
||||
|
||||
describe('mobile and open', () => {
|
||||
it('should render sheet', () => {
|
||||
reduxHooks.useHasCourses.mockReturnValue(true);
|
||||
useCourseFilterControlsData.mockReturnValue({ ...mockControlsData, isOpen: true });
|
||||
useCourseFilterControlsData.mockReturnValue({
|
||||
isOpen: false,
|
||||
open: jest.fn().mockName('open'),
|
||||
close: jest.fn().mockName('close'),
|
||||
target: 'test-target',
|
||||
setTarget: jest.fn().mockName('setTarget'),
|
||||
handleFilterChange: jest.fn().mockName('handleFilterChange'),
|
||||
handleSortChange: jest.fn().mockName('handleSortChange'),
|
||||
});
|
||||
|
||||
describe('no courses', () => {
|
||||
test('snapshot', () => {
|
||||
reduxHooks.useHasCourses.mockReturnValueOnce(false);
|
||||
useWindowSize.mockReturnValueOnce({ width: breakpoints.small.minWidth });
|
||||
const wrapper = shallow(<CourseFilterControls {...props} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
describe('mobile', () => {
|
||||
test('snapshot', () => {
|
||||
useWindowSize.mockReturnValueOnce({ width: breakpoints.small.minWidth - 1 });
|
||||
render(<IntlProvider locale="en"><CourseFilterControls {...props} /></IntlProvider>);
|
||||
const sheet = screen.getByRole('presentation', { hidden: true });
|
||||
expect(sheet).toBeInTheDocument();
|
||||
expect(sheet.parentElement).toHaveClass('sheet-container');
|
||||
const wrapper = shallow(<CourseFilterControls {...props} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
describe('is not mobile', () => {
|
||||
it('should have button disabled', () => {
|
||||
reduxHooks.useHasCourses.mockReturnValue(true);
|
||||
useCourseFilterControlsData.mockReturnValue({ ...mockControlsData, isOpen: true });
|
||||
test('snapshot', () => {
|
||||
useWindowSize.mockReturnValueOnce({ width: breakpoints.small.minWidth });
|
||||
render(<IntlProvider locale="en"><CourseFilterControls {...props} /></IntlProvider>);
|
||||
const filterForm = screen.getByText(messages.courseStatus.defaultMessage);
|
||||
const modal = filterForm.closest('div.pgn__modal-popup__tooltip');
|
||||
expect(modal).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
describe('no courses', () => {
|
||||
it('should have button disabled', () => {
|
||||
reduxHooks.useHasCourses.mockReturnValue(false);
|
||||
useCourseFilterControlsData.mockReturnValue(mockControlsData);
|
||||
useWindowSize.mockReturnValue({ width: breakpoints.small.minWidth });
|
||||
render(<IntlProvider locale="en"><CourseFilterControls {...props} /></IntlProvider>);
|
||||
const button = screen.getByRole('button', { name: formatMessage(messages.refine) });
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).toBeDisabled();
|
||||
const wrapper = shallow(<CourseFilterControls {...props} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ActiveCourseFilters snapshot renders 1`] = `
|
||||
<div
|
||||
id="course-list-active-filters"
|
||||
>
|
||||
<Chip
|
||||
key="inProgress"
|
||||
>
|
||||
In-Progress
|
||||
</Chip>
|
||||
<Chip
|
||||
key="notStarted"
|
||||
>
|
||||
Not Started
|
||||
</Chip>
|
||||
<Chip
|
||||
key="done"
|
||||
>
|
||||
Done
|
||||
</Chip>
|
||||
<Chip
|
||||
key="notEnrolled"
|
||||
>
|
||||
Not Enrolled
|
||||
</Chip>
|
||||
<Chip
|
||||
key="upgraded"
|
||||
>
|
||||
Upgraded
|
||||
</Chip>
|
||||
<Button
|
||||
onClick={[Function]}
|
||||
variant="link"
|
||||
>
|
||||
Clear all
|
||||
</Button>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,169 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CourseFilterControls is not mobile snapshot 1`] = `
|
||||
<div
|
||||
id="course-filter-controls"
|
||||
>
|
||||
<Button
|
||||
disabled={false}
|
||||
iconBefore={[MockFunction icons.Tune]}
|
||||
onClick={[MockFunction open]}
|
||||
variant="outline-primary"
|
||||
>
|
||||
Refine
|
||||
</Button>
|
||||
<Form>
|
||||
<ModalPopup
|
||||
isOpen={false}
|
||||
onClose={[MockFunction close]}
|
||||
placement="bottom-end"
|
||||
positionRef="test-target"
|
||||
>
|
||||
<div
|
||||
className="bg-white p-3 rounded shadow d-flex flex-row"
|
||||
id="course-filter-controls-card"
|
||||
>
|
||||
<div
|
||||
className="filter-form-col"
|
||||
>
|
||||
<FilterForm
|
||||
filters={
|
||||
[
|
||||
"test-filter",
|
||||
]
|
||||
}
|
||||
handleFilterChange={[MockFunction handleFilterChange]}
|
||||
/>
|
||||
</div>
|
||||
<hr
|
||||
className="h-100 bg-primary-200 mx-3 my-0"
|
||||
/>
|
||||
<div
|
||||
className="filter-form-col text-left m-1"
|
||||
>
|
||||
<SortForm
|
||||
handleSortChange={[MockFunction handleSortChange]}
|
||||
sortBy="test-sort-by"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ModalPopup>
|
||||
</Form>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`CourseFilterControls mobile snapshot 1`] = `
|
||||
<div
|
||||
id="course-filter-controls"
|
||||
>
|
||||
<Button
|
||||
disabled={false}
|
||||
iconBefore={[MockFunction icons.Tune]}
|
||||
onClick={[MockFunction open]}
|
||||
variant="outline-primary"
|
||||
>
|
||||
Refine
|
||||
</Button>
|
||||
<Form>
|
||||
<Sheet
|
||||
className="w-75"
|
||||
onClose={[MockFunction close]}
|
||||
position="left"
|
||||
show={false}
|
||||
>
|
||||
<div
|
||||
className="p-1 mr-3"
|
||||
>
|
||||
<b>
|
||||
Refine
|
||||
</b>
|
||||
</div>
|
||||
<hr />
|
||||
<div
|
||||
className="filter-form-row"
|
||||
>
|
||||
<FilterForm
|
||||
filters={
|
||||
[
|
||||
"test-filter",
|
||||
]
|
||||
}
|
||||
handleFilterChange={[MockFunction handleFilterChange]}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="filter-form-row text-left m-1"
|
||||
>
|
||||
<SortForm
|
||||
handleSortChange={[MockFunction handleSortChange]}
|
||||
sortBy="test-sort-by"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="pgn__modal-close-container"
|
||||
>
|
||||
<ModalCloseButton
|
||||
onClick={[MockFunction close]}
|
||||
variant="tertiary"
|
||||
>
|
||||
<Icon
|
||||
src={[MockFunction icons.Close]}
|
||||
/>
|
||||
</ModalCloseButton>
|
||||
</div>
|
||||
</Sheet>
|
||||
</Form>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`CourseFilterControls no courses snapshot 1`] = `
|
||||
<div
|
||||
id="course-filter-controls"
|
||||
>
|
||||
<Button
|
||||
disabled={true}
|
||||
iconBefore={[MockFunction icons.Tune]}
|
||||
onClick={[MockFunction open]}
|
||||
variant="outline-primary"
|
||||
>
|
||||
Refine
|
||||
</Button>
|
||||
<Form>
|
||||
<ModalPopup
|
||||
isOpen={false}
|
||||
onClose={[MockFunction close]}
|
||||
placement="bottom-end"
|
||||
positionRef="test-target"
|
||||
>
|
||||
<div
|
||||
className="bg-white p-3 rounded shadow d-flex flex-row"
|
||||
id="course-filter-controls-card"
|
||||
>
|
||||
<div
|
||||
className="filter-form-col"
|
||||
>
|
||||
<FilterForm
|
||||
filters={
|
||||
[
|
||||
"test-filter",
|
||||
]
|
||||
}
|
||||
handleFilterChange={[MockFunction handleFilterChange]}
|
||||
/>
|
||||
</div>
|
||||
<hr
|
||||
className="h-100 bg-primary-200 mx-3 my-0"
|
||||
/>
|
||||
<div
|
||||
className="filter-form-col text-left m-1"
|
||||
>
|
||||
<SortForm
|
||||
handleSortChange={[MockFunction handleSortChange]}
|
||||
sortBy="test-sort-by"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ModalPopup>
|
||||
</Form>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,17 +1,14 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { formatMessage } from 'testUtils';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { FilterKeys } from 'data/constants/app';
|
||||
import Checkbox from './Checkbox';
|
||||
import messages from '../messages';
|
||||
|
||||
describe('Checkbox', () => {
|
||||
describe('renders correctly', () => {
|
||||
describe('snapshot', () => {
|
||||
Object.keys(FilterKeys).forEach((filterKey) => {
|
||||
it(`renders ${filterKey}`, () => {
|
||||
render(<IntlProvider locale="en"><Checkbox filterKey={filterKey} /></IntlProvider>);
|
||||
expect(screen.getByText(formatMessage(messages[filterKey]))).toBeInTheDocument();
|
||||
const wrapper = shallow(<Checkbox filterKey={filterKey} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,54 +1,29 @@
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { formatMessage } from 'testUtils';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { FilterKeys } from 'data/constants/app';
|
||||
import { FilterForm, filterOrder } from './FilterForm';
|
||||
import messages from '../messages';
|
||||
import FilterForm, { filterOrder } from './FilterForm';
|
||||
|
||||
const mockHandleFilterChange = jest.fn();
|
||||
|
||||
const defaultProps = {
|
||||
filters: [FilterKeys.inProgress],
|
||||
handleFilterChange: mockHandleFilterChange,
|
||||
};
|
||||
|
||||
const renderComponent = (props = defaultProps) => render(
|
||||
<IntlProvider messages={{}}>
|
||||
<FilterForm {...props} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
jest.mock('./Checkbox', () => 'Checkbox');
|
||||
|
||||
describe('FilterForm', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders all filter checkboxes in correct order', () => {
|
||||
renderComponent();
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
expect(checkboxes).toHaveLength(filterOrder.length);
|
||||
checkboxes.forEach((checkbox, index) => {
|
||||
expect(checkbox).toHaveAttribute('value', filterOrder[index]);
|
||||
const props = {
|
||||
filters: ['test-filter'],
|
||||
handleFilterChange: jest.fn().mockName('handleFilterChange'),
|
||||
};
|
||||
describe('snapshot', () => {
|
||||
test('renders', () => {
|
||||
const wrapper = shallow(<FilterForm {...props} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it('checks boxes based on filters prop', () => {
|
||||
const filters = [FilterKeys.inProgress, FilterKeys.done];
|
||||
renderComponent({ ...defaultProps, filters });
|
||||
filters.forEach(filter => {
|
||||
expect(screen.getByRole('checkbox', { name: formatMessage(messages[filter]) })).toBeChecked();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls handleFilterChange when checkbox is clicked', () => {
|
||||
renderComponent();
|
||||
const checkbox = screen.getByRole('checkbox', { name: formatMessage(messages.notStarted) });
|
||||
fireEvent.click(checkbox);
|
||||
expect(mockHandleFilterChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('displays course status heading', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByText(/course status/i)).toBeInTheDocument();
|
||||
test('filterOrder', () => {
|
||||
expect(filterOrder).toEqual([
|
||||
FilterKeys.inProgress,
|
||||
FilterKeys.notStarted,
|
||||
FilterKeys.done,
|
||||
FilterKeys.notEnrolled,
|
||||
FilterKeys.upgraded,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,29 +1,19 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { formatMessage } from 'testUtils';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { SortKeys } from 'data/constants/app';
|
||||
import SortForm from './SortForm';
|
||||
import messages from '../messages';
|
||||
|
||||
jest.mock('./Checkbox', () => 'Checkbox');
|
||||
|
||||
describe('SortForm', () => {
|
||||
const props = {
|
||||
handleSortChange: jest.fn().mockName('handleSortChange'),
|
||||
sortBy: SortKeys.enrolled,
|
||||
};
|
||||
it('renders heading', () => {
|
||||
render(<IntlProvider locale="en"><SortForm {...props} /></IntlProvider>);
|
||||
const heading = screen.getByText(formatMessage(messages.sort));
|
||||
expect(heading).toBeInTheDocument();
|
||||
});
|
||||
it('renders radio enrolled', () => {
|
||||
render(<IntlProvider locale="en"><SortForm {...props} /></IntlProvider>);
|
||||
const enrolled = screen.getByRole('radio', { name: formatMessage(messages.sortLastEnrolled) });
|
||||
expect(enrolled).toBeInTheDocument();
|
||||
});
|
||||
it('renders radio title', () => {
|
||||
render(<IntlProvider locale="en"><SortForm {...props} /></IntlProvider>);
|
||||
const title = screen.getByRole('radio', { name: formatMessage(messages.sortTitle) });
|
||||
expect(title).toBeInTheDocument();
|
||||
describe('snapshot', () => {
|
||||
test('renders', () => {
|
||||
const wrapper = shallow(<SortForm {...props} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Checkbox snapshot renders done 1`] = `
|
||||
<Form.Checkbox
|
||||
className="py-2"
|
||||
value="done"
|
||||
>
|
||||
Done
|
||||
</Form.Checkbox>
|
||||
`;
|
||||
|
||||
exports[`Checkbox snapshot renders inProgress 1`] = `
|
||||
<Form.Checkbox
|
||||
className="py-2"
|
||||
value="inProgress"
|
||||
>
|
||||
In-Progress
|
||||
</Form.Checkbox>
|
||||
`;
|
||||
|
||||
exports[`Checkbox snapshot renders notEnrolled 1`] = `
|
||||
<Form.Checkbox
|
||||
className="py-2"
|
||||
value="notEnrolled"
|
||||
>
|
||||
Not Enrolled
|
||||
</Form.Checkbox>
|
||||
`;
|
||||
|
||||
exports[`Checkbox snapshot renders notStarted 1`] = `
|
||||
<Form.Checkbox
|
||||
className="py-2"
|
||||
value="notStarted"
|
||||
>
|
||||
Not Started
|
||||
</Form.Checkbox>
|
||||
`;
|
||||
|
||||
exports[`Checkbox snapshot renders upgraded 1`] = `
|
||||
<Form.Checkbox
|
||||
className="py-2"
|
||||
value="upgraded"
|
||||
>
|
||||
Upgraded
|
||||
</Form.Checkbox>
|
||||
`;
|
||||
@@ -0,0 +1,41 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`FilterForm snapshot renders 1`] = `
|
||||
<Form.Group>
|
||||
<div
|
||||
className="filter-form-heading mb-1"
|
||||
>
|
||||
Course Status
|
||||
</div>
|
||||
<Form.CheckboxSet
|
||||
name="course-status-filters"
|
||||
onChange={[MockFunction handleFilterChange]}
|
||||
value={
|
||||
[
|
||||
"test-filter",
|
||||
]
|
||||
}
|
||||
>
|
||||
<Checkbox
|
||||
filterKey="inProgress"
|
||||
key="inProgress"
|
||||
/>
|
||||
<Checkbox
|
||||
filterKey="notStarted"
|
||||
key="notStarted"
|
||||
/>
|
||||
<Checkbox
|
||||
filterKey="done"
|
||||
key="done"
|
||||
/>
|
||||
<Checkbox
|
||||
filterKey="notEnrolled"
|
||||
key="notEnrolled"
|
||||
/>
|
||||
<Checkbox
|
||||
filterKey="upgraded"
|
||||
key="upgraded"
|
||||
/>
|
||||
</Form.CheckboxSet>
|
||||
</Form.Group>
|
||||
`;
|
||||
@@ -0,0 +1,29 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SortForm snapshot renders 1`] = `
|
||||
<Fragment>
|
||||
<div
|
||||
className="filter-form-heading mb-1"
|
||||
>
|
||||
Sort
|
||||
</div>
|
||||
<Form.RadioSet
|
||||
name="sort"
|
||||
onChange={[MockFunction handleSortChange]}
|
||||
value="enrolled"
|
||||
>
|
||||
<Form.Radio
|
||||
className="py-2"
|
||||
value="enrolled"
|
||||
>
|
||||
Last enrolled
|
||||
</Form.Radio>
|
||||
<Form.Radio
|
||||
className="py-2"
|
||||
value="title"
|
||||
>
|
||||
Title (A-Z)
|
||||
</Form.Radio>
|
||||
</Form.RadioSet>
|
||||
</Fragment>
|
||||
`;
|
||||
@@ -7,15 +7,6 @@ import track from 'tracking';
|
||||
|
||||
import * as hooks from './hooks';
|
||||
|
||||
jest.mock('@openedx/paragon', () => ({
|
||||
...jest.requireActual('@openedx/paragon'),
|
||||
useToggle: jest.fn().mockImplementation((val) => [
|
||||
val,
|
||||
jest.fn().mockName('useToggle.setTrue'),
|
||||
jest.fn().mockName('useToggle.setFalse'),
|
||||
]),
|
||||
}));
|
||||
|
||||
jest.mock('tracking', () => ({
|
||||
filter: {
|
||||
filterClicked: jest.fn(),
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CourseList collapsed with multiple courses and pages snapshot 1`] = `
|
||||
<Fragment>
|
||||
<div
|
||||
id="course-list-active-filters-container"
|
||||
>
|
||||
<ActiveCourseFilters />
|
||||
</div>
|
||||
<div
|
||||
className="d-flex flex-column flex-grow-1"
|
||||
>
|
||||
<CourseCard
|
||||
cardId="foo"
|
||||
key="foo"
|
||||
/>
|
||||
<CourseCard
|
||||
cardId="bar"
|
||||
key="bar"
|
||||
/>
|
||||
<CourseCard
|
||||
cardId="baz"
|
||||
key="baz"
|
||||
/>
|
||||
<Pagination
|
||||
className="mx-auto mb-2"
|
||||
pageCount={3}
|
||||
paginationLabel="Course List"
|
||||
variant="reduced"
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`CourseList no courses or filters snapshot 1`] = `
|
||||
<Fragment>
|
||||
<div
|
||||
className="d-flex flex-column flex-grow-1"
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`CourseList with filters snapshot 1`] = `undefined`;
|
||||
|
||||
exports[`CourseList with multiple courses and pages snapshot 1`] = `
|
||||
<Fragment>
|
||||
<div
|
||||
className="d-flex flex-column flex-grow-1"
|
||||
>
|
||||
<CourseCard
|
||||
cardId="foo"
|
||||
key="foo"
|
||||
/>
|
||||
<CourseCard
|
||||
cardId="bar"
|
||||
key="bar"
|
||||
/>
|
||||
<CourseCard
|
||||
cardId="baz"
|
||||
key="baz"
|
||||
/>
|
||||
<Pagination
|
||||
className="mx-auto mb-2"
|
||||
pageCount={3}
|
||||
paginationLabel="Course List"
|
||||
variant="secondary"
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
`;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { useIsCollapsed } from './hooks';
|
||||
import CourseList from '.';
|
||||
@@ -7,9 +7,9 @@ jest.mock('./hooks', () => ({
|
||||
useIsCollapsed: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('containers/CourseCard', () => jest.fn(() => <div>CourseCard</div>));
|
||||
jest.mock('containers/CourseCard', () => 'CourseCard');
|
||||
jest.mock('containers/CourseFilterControls', () => ({
|
||||
ActiveCourseFilters: jest.fn(() => <div>ActiveCourseFilters</div>),
|
||||
ActiveCourseFilters: 'ActiveCourseFilters',
|
||||
}));
|
||||
|
||||
describe('CourseList', () => {
|
||||
@@ -22,60 +22,43 @@ describe('CourseList', () => {
|
||||
};
|
||||
useIsCollapsed.mockReturnValue(false);
|
||||
|
||||
const renderList = (courseListData = defaultCourseListData) => (
|
||||
render(<CourseList courseListData={courseListData} />)
|
||||
const createWrapper = (courseListData = defaultCourseListData) => (
|
||||
shallow(<CourseList courseListData={courseListData} />)
|
||||
);
|
||||
|
||||
describe('no courses or filters', () => {
|
||||
it('should not render related components', () => {
|
||||
renderList();
|
||||
const filterControls = screen.queryByText('ActiveCourseFilters');
|
||||
const courseCard = screen.queryByText('CourseCard');
|
||||
const prevButton = screen.queryByRole('button', { name: 'Previous' });
|
||||
expect(filterControls).toBeNull();
|
||||
expect(courseCard).toBeNull();
|
||||
expect(prevButton).toBeNull();
|
||||
test('snapshot', () => {
|
||||
const wrapper = createWrapper();
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
describe('with filters', () => {
|
||||
it('should render filter component', () => {
|
||||
renderList({
|
||||
...defaultCourseListData,
|
||||
test('snapshot', () => {
|
||||
const wrapper = createWrapper({
|
||||
filterOptions: { abitary: 'filter' },
|
||||
showFilters: true,
|
||||
});
|
||||
const filterControls = screen.getByText('ActiveCourseFilters');
|
||||
expect(filterControls).toBeInTheDocument();
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
describe('with multiple courses and pages', () => {
|
||||
it('render Course Cards and pagination', () => {
|
||||
const visibleList = [{ cardId: 'foo' }, { cardId: 'bar' }, { cardId: 'baz' }];
|
||||
const numPages = 3;
|
||||
renderList({
|
||||
...defaultCourseListData,
|
||||
visibleList,
|
||||
numPages,
|
||||
test('snapshot', () => {
|
||||
const wrapper = createWrapper({
|
||||
visibleList: [{ cardId: 'foo' }, { cardId: 'bar' }, { cardId: 'baz' }],
|
||||
numPages: 3,
|
||||
});
|
||||
const courseCards = screen.getAllByText('CourseCard');
|
||||
expect(courseCards.length).toEqual(visibleList.length);
|
||||
const pageButtons = screen.getAllByRole('button', { name: /^Page/i });
|
||||
expect(pageButtons.length).toBe(numPages);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
describe('collapsed with multiple courses and pages', () => {
|
||||
it('should render correct components', () => {
|
||||
const visibleList = [{ cardId: 'foo' }, { cardId: 'bar' }, { cardId: 'baz' }];
|
||||
test('snapshot', () => {
|
||||
useIsCollapsed.mockReturnValueOnce(true);
|
||||
renderList({
|
||||
...defaultCourseListData,
|
||||
visibleList,
|
||||
const wrapper = createWrapper({
|
||||
visibleList: [{ cardId: 'foo' }, { cardId: 'bar' }, { cardId: 'baz' }],
|
||||
numPages: 3,
|
||||
showFilters: true,
|
||||
});
|
||||
const courseCards = screen.getAllByText('CourseCard');
|
||||
expect(courseCards.length).toEqual(visibleList.length);
|
||||
const reducedPagination = screen.getByRole('button', { name: '1 of 3' });
|
||||
expect(reducedPagination).toBeInTheDocument();
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`NoCoursesView snapshot 1`] = `
|
||||
<div
|
||||
className="d-flex align-items-center justify-content-center mb-4.5"
|
||||
id="no-courses-content-view"
|
||||
>
|
||||
<Image
|
||||
alt="No Courses view banner"
|
||||
src="icon/mock/path"
|
||||
/>
|
||||
<h3
|
||||
className="h1"
|
||||
>
|
||||
Looking for a new challenge?
|
||||
</h3>
|
||||
<p>
|
||||
Explore our courses to add them to your dashboard.
|
||||
</p>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/course-search-url"
|
||||
iconBefore={[MockFunction icons.Search]}
|
||||
variant="brand"
|
||||
>
|
||||
Explore courses
|
||||
</Button>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,13 +1,15 @@
|
||||
@import "@openedx/paragon/scss/core/core";
|
||||
|
||||
#no-courses-content-view {
|
||||
border: 2px solid var(--pgn-color-light-400);
|
||||
border: 2px solid $light-400;
|
||||
flex-direction: column;
|
||||
padding-bottom: var(--pgn-spacing-spacer-5);
|
||||
padding-top: var(--pgn-spacing-spacer-5);
|
||||
padding-bottom: map-get($spacers, 5);
|
||||
padding-top: map-get($spacers, 5);
|
||||
height: 100%;
|
||||
|
||||
& > * {
|
||||
margin-top: var(--pgn-spacing-spacer-3);
|
||||
margin-bottom: var(--pgn-spacing-spacer-3);
|
||||
margin-top: map-get($spacers, 3);
|
||||
margin-bottom: map-get($spacers, 3);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,38 +1,18 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { formatMessage } from 'testUtils';
|
||||
import { baseAppUrl } from 'data/services/lms/urls';
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import EmptyCourse from '.';
|
||||
import messages from './messages';
|
||||
|
||||
const courseSearchUrl = '/course-search-url';
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
usePlatformSettingsData: jest.fn(() => ({
|
||||
courseSearchUrl,
|
||||
courseSearchUrl: '/course-search-url',
|
||||
})),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('NoCoursesView', () => {
|
||||
it('should display image, heading and button', () => {
|
||||
render(<IntlProvider locale="en"><EmptyCourse /></IntlProvider>);
|
||||
const image = screen.getByRole('img', { alt: formatMessage(messages.bannerAlt) });
|
||||
expect(image).toBeInTheDocument();
|
||||
});
|
||||
it('should display heading and prompt', () => {
|
||||
render(<IntlProvider locale="en"><EmptyCourse /></IntlProvider>);
|
||||
const heading = screen.getByText(formatMessage(messages.lookingForChallengePrompt));
|
||||
const prompt = screen.getByText(formatMessage(messages.exploreCoursesPrompt));
|
||||
expect(heading).toBeInTheDocument();
|
||||
expect(prompt).toBeInTheDocument();
|
||||
});
|
||||
it('should display button', () => {
|
||||
render(<IntlProvider locale="en"><EmptyCourse /></IntlProvider>);
|
||||
const button = screen.getByRole('link', { name: formatMessage(messages.exploreCoursesButton) });
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button.href).toBe(baseAppUrl(courseSearchUrl));
|
||||
test('snapshot', () => {
|
||||
expect(shallow(<EmptyCourse />).snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CoursesPanel no courses snapshot 1`] = `
|
||||
<div
|
||||
className="course-list-container"
|
||||
>
|
||||
<div
|
||||
className="course-list-heading-container"
|
||||
>
|
||||
<h2
|
||||
className="course-list-title"
|
||||
>
|
||||
My Courses
|
||||
</h2>
|
||||
<div
|
||||
className="course-filter-controls-container"
|
||||
>
|
||||
<CourseFilterControls />
|
||||
</div>
|
||||
</div>
|
||||
<NoCoursesViewSlot />
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`CoursesPanel with courses snapshot 1`] = `
|
||||
<div
|
||||
className="course-list-container"
|
||||
>
|
||||
<div
|
||||
className="course-list-heading-container"
|
||||
>
|
||||
<h2
|
||||
className="course-list-title"
|
||||
>
|
||||
My Courses
|
||||
</h2>
|
||||
<div
|
||||
className="course-filter-controls-container"
|
||||
>
|
||||
<CourseFilterControls />
|
||||
</div>
|
||||
</div>
|
||||
<CourseListSlot
|
||||
courseListData={
|
||||
{
|
||||
"filterOptions": {},
|
||||
"numPages": 1,
|
||||
"setPageNumber": [MockFunction setPageNumber],
|
||||
"showFilters": false,
|
||||
"visibleList": [],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,3 +1,5 @@
|
||||
@import "@openedx/paragon/scss/core/core";
|
||||
|
||||
.course-list-heading-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -6,8 +8,8 @@
|
||||
}
|
||||
|
||||
.course-list-title {
|
||||
font-size: var(--pgn-typography-font-size-h2-base);
|
||||
margin: var(--pgn-spacing-spacer-3) 0;
|
||||
font-size: $h2-font-size;
|
||||
margin: map-get($spacers, 3) 0;
|
||||
}
|
||||
|
||||
.course-list-loading {
|
||||
@@ -22,7 +24,7 @@
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
@media (--pgn-size-breakpoint-max-width-md) {
|
||||
@include media-breakpoint-down(md) {
|
||||
.course-list-heading-container {
|
||||
flex-direction: column-reverse;
|
||||
align-items: flex-start;
|
||||
@@ -34,7 +36,7 @@
|
||||
}
|
||||
|
||||
.course-list-title {
|
||||
font-size: var(--pgn-typography-font-size-h3-base);
|
||||
margin: var(--pgn-spacing-spacer-2) 0;
|
||||
font-size: $h3-font-size;
|
||||
margin: map-get($spacers, 2) 0;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user