Compare commits
49 Commits
jodybailey
...
schen/upse
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdb0f48f08 | ||
|
|
3cdcc1fe61 | ||
|
|
82ff0d7ddb | ||
|
|
1478956e34 | ||
|
|
f049712430 | ||
|
|
c977de2df9 | ||
|
|
4b20c5bbdd | ||
|
|
0c1fa2f030 | ||
|
|
91117cce6a | ||
|
|
e6dba8bdc2 | ||
|
|
1d67ac5f24 | ||
|
|
60d2f22c50 | ||
|
|
5dc89d7404 | ||
|
|
0f24d3a52d | ||
|
|
fc885d02dc | ||
|
|
2e09d3632e | ||
|
|
d8cb46da60 | ||
|
|
199d6e7c60 | ||
|
|
64563d58f9 | ||
|
|
1e9a0a87b6 | ||
|
|
d42d0cdc59 | ||
|
|
8fef92d94d | ||
|
|
b41eee47c9 | ||
|
|
909f3f1f47 | ||
|
|
ce269e8c8f | ||
|
|
86a4573405 | ||
|
|
be2258e409 | ||
|
|
be8cb85773 | ||
|
|
a2c003e542 | ||
|
|
f1cfe3de68 | ||
|
|
d43c17a663 | ||
|
|
c01042f1df | ||
|
|
ed2368222f | ||
|
|
103a67654c | ||
|
|
58c3720087 | ||
|
|
4e47018a81 | ||
|
|
e7d9255fe5 | ||
|
|
2c7e10ffc2 | ||
|
|
43aa5b088e | ||
|
|
86b1f5df1a | ||
|
|
5c52b6861e | ||
|
|
a358a6014f | ||
|
|
6ebc94506b | ||
|
|
59ab63807f | ||
|
|
322a79afaa | ||
|
|
c458f4942f | ||
|
|
93a4dfb4d9 | ||
|
|
f92bd9c8f9 | ||
|
|
5db95b0029 |
2
.env
2
.env
@@ -40,3 +40,5 @@ ACCOUNT_SETTINGS_URL=''
|
||||
ACCOUNT_PROFILE_URL=''
|
||||
ENABLE_NOTICES=''
|
||||
CAREER_LINK_URL=''
|
||||
OPTIMIZELY_FULL_STACK_SDK_KEY=''
|
||||
EXPERIMENT_08_23_VAN_PAINTED_DOOR='true'
|
||||
|
||||
@@ -47,3 +47,5 @@ ACCOUNT_SETTINGS_URL='http://localhost:1997'
|
||||
ACCOUNT_PROFILE_URL='http://localhost:1995'
|
||||
ENABLE_NOTICES=''
|
||||
CAREER_LINK_URL=''
|
||||
OPTIMIZELY_FULL_STACK_SDK_KEY=''
|
||||
EXPERIMENT_08_23_VAN_PAINTED_DOOR=true
|
||||
|
||||
@@ -46,3 +46,5 @@ ACCOUNT_SETTINGS_URL='http://account-settings-url.test'
|
||||
ACCOUNT_PROFILE_URL='http://account-profile-url.test'
|
||||
ENABLE_NOTICES=''
|
||||
CAREER_LINK_URL=''
|
||||
OPTIMIZELY_FULL_STACK_SDK_KEY='SDK Key'
|
||||
EXPERIMENT_08_23_VAN_PAINTED_DOOR=true
|
||||
|
||||
@@ -5,6 +5,7 @@ const config = createConfig('eslint', {
|
||||
'import/no-named-as-default': 'off',
|
||||
'import/no-named-as-default-member': 'off',
|
||||
'import/no-self-import': 'off',
|
||||
'import/no-import-module-exports': 'off',
|
||||
'spaced-comment': ['error', 'always', { 'block': { 'exceptions': ['*'] } }],
|
||||
},
|
||||
});
|
||||
|
||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -49,9 +49,9 @@ jobs:
|
||||
server_port: 465
|
||||
username: ${{ secrets.EDX_SMTP_USERNAME }}
|
||||
password: ${{ secrets.EDX_SMTP_PASSWORD }}
|
||||
subject: Upgrade python requirements workflow failed in ${{github.repository}}
|
||||
subject: CI workflow failed in ${{github.repository}}
|
||||
to: masters-grades@edx.org
|
||||
from: github-actions <github-actions@edx.org>
|
||||
body: Upgrade python requirements workflow in ${{github.repository}} failed!
|
||||
body: CI workflow in ${{github.repository}} failed!
|
||||
For details see "github.com/${{ github.repository }}/actions/runs/${{ github.run_id
|
||||
}}"
|
||||
|
||||
3
Makefile
3
Makefile
@@ -64,10 +64,11 @@ pull_translations:
|
||||
mkdir src/i18n/messages
|
||||
cd src/i18n/messages \
|
||||
&& atlas pull --filter=$(transifex_langs) \
|
||||
translations/paragon/src/i18n/messages:paragon \
|
||||
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
|
||||
translations/frontend-app-learner-dashboard/src/i18n/messages:frontend-app-learner-dashboard
|
||||
|
||||
$(intl_imports) frontend-component-footer frontend-app-learner-dashboard
|
||||
$(intl_imports) paragon frontend-component-footer frontend-app-learner-dashboard
|
||||
endif
|
||||
|
||||
# This target is used by CI.
|
||||
|
||||
8420
package-lock.json
generated
8420
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@@ -28,14 +28,16 @@
|
||||
"dependencies": {
|
||||
"@edx/brand": "npm:@edx/brand-edx.org@^2.0.3",
|
||||
"@edx/browserslist-config": "^1.1.0",
|
||||
"@edx/frontend-component-footer": "^12.0.0",
|
||||
"@edx/frontend-enterprise-hotjar": "^1.2.0",
|
||||
"@edx/frontend-platform": "^4.2.0",
|
||||
"@edx/paragon": "^20.32.0",
|
||||
"@edx/frontend-component-footer": "^12.2.1",
|
||||
"@edx/frontend-enterprise-hotjar": "^2.0.0",
|
||||
"@edx/frontend-platform": "^5.5.4",
|
||||
"@edx/paragon": "^20.44.0",
|
||||
"@edx/react-unit-test-utils": "^1.7.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.1.15",
|
||||
"@optimizely/react-sdk": "^2.9.2",
|
||||
"@redux-beacon/segment": "^1.1.0",
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
@@ -52,17 +54,18 @@
|
||||
"history": "5.0.1",
|
||||
"html-react-parser": "^1.3.0",
|
||||
"jest": "^26.6.3",
|
||||
"jest-when": "^3.6.0",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"prop-types": "15.7.2",
|
||||
"query-string": "7.0.1",
|
||||
"react": "^16.14.0",
|
||||
"react-dom": "^16.14.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-intl": "^5.20.9",
|
||||
"react-pdf": "^5.5.0",
|
||||
"react-redux": "^7.2.4",
|
||||
"react-router-dom": "5.3.3",
|
||||
"react-router-dom": "6.15.0",
|
||||
"react-share": "^4.4.0",
|
||||
"react-zendesk": "^0.1.13",
|
||||
"redux": "4.1.1",
|
||||
@@ -81,15 +84,15 @@
|
||||
"@edx/reactifex": "^2.1.1",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^12.1.0",
|
||||
"@wojtekmaj/enzyme-adapter-react-17": "0.8.0",
|
||||
"axios-mock-adapter": "^1.20.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"enzyme-adapter-react-16": "^1.15.6",
|
||||
"fetch-mock": "^9.11.0",
|
||||
"husky": "^7.0.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest-expect-message": "^1.0.2",
|
||||
"react-dev-utils": "^11.0.4",
|
||||
"react-test-renderer": "^16.14.0",
|
||||
"react-test-renderer": "^17.0.2",
|
||||
"redux-mock-store": "^1.5.4",
|
||||
"semantic-release": "^20.1.3"
|
||||
}
|
||||
|
||||
@@ -5,6 +5,15 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon" />
|
||||
<% if (process.env.OPTIMIZELY_URL) { %>
|
||||
<script
|
||||
src="<%= process.env.OPTIMIZELY_URL %>"
|
||||
></script>
|
||||
<% } else if (process.env.OPTIMIZELY_PROJECT_ID) { %>
|
||||
<script
|
||||
src="<%= process.env.MARKETING_SITE_BASE_URL %>/optimizelyjs/<%= process.env.OPTIMIZELY_PROJECT_ID %>.js"
|
||||
></script>
|
||||
<% } %>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
35
src/App.jsx
35
src/App.jsx
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
@@ -19,14 +18,16 @@ import {
|
||||
import { reduxHooks } from 'hooks';
|
||||
import Dashboard from 'containers/Dashboard';
|
||||
import ZendeskFab from 'components/ZendeskFab';
|
||||
import { ExperimentProvider } from 'ExperimentContext';
|
||||
|
||||
import track from 'tracking';
|
||||
|
||||
import fakeData from 'data/services/lms/fakeData/courses';
|
||||
import LearnerDashboardHeader from './containers/LearnerDashboardHeader';
|
||||
|
||||
import AppWrapper from 'containers/WidgetContainers/AppWrapper';
|
||||
import LearnerDashboardHeader from 'containers/LearnerDashboardHeader';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
import './App.scss';
|
||||
|
||||
export const App = () => {
|
||||
@@ -72,24 +73,30 @@ export const App = () => {
|
||||
}
|
||||
}, [authenticatedUser, loadData]);
|
||||
return (
|
||||
<Router>
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{formatMessage(messages.pageTitle)}</title>
|
||||
</Helmet>
|
||||
<div>
|
||||
<LearnerDashboardHeader />
|
||||
<main>
|
||||
{hasNetworkFailure
|
||||
? (
|
||||
<Alert variant="danger">
|
||||
<ErrorPage message={formatMessage(messages.errorMessage, { supportEmail })} />
|
||||
</Alert>
|
||||
) : (<Dashboard />)}
|
||||
</main>
|
||||
<AppWrapper>
|
||||
<LearnerDashboardHeader />
|
||||
<main>
|
||||
{hasNetworkFailure
|
||||
? (
|
||||
<Alert variant="danger">
|
||||
<ErrorPage message={formatMessage(messages.errorMessage, { supportEmail })} />
|
||||
</Alert>
|
||||
) : (
|
||||
<ExperimentProvider>
|
||||
<Dashboard />
|
||||
</ExperimentProvider>
|
||||
)}
|
||||
</main>
|
||||
</AppWrapper>
|
||||
<Footer logo={process.env.LOGO_POWERED_BY_OPEN_EDX_URL_SVG} />
|
||||
<ZendeskFab />
|
||||
</div>
|
||||
</Router>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { ErrorPage } from '@edx/frontend-platform/react';
|
||||
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import Footer from '@edx/frontend-component-footer';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Alert } from '@edx/paragon';
|
||||
|
||||
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 { ExperimentProvider } from 'ExperimentContext';
|
||||
import { App } from './App';
|
||||
import messages from './messages';
|
||||
|
||||
@@ -21,6 +19,10 @@ jest.mock('@edx/frontend-component-footer', () => 'Footer');
|
||||
jest.mock('containers/Dashboard', () => 'Dashboard');
|
||||
jest.mock('containers/LearnerDashboardHeader', () => 'LearnerDashboardHeader');
|
||||
jest.mock('components/ZendeskFab', () => 'ZendeskFab');
|
||||
jest.mock('ExperimentContext', () => ({
|
||||
ExperimentProvider: 'ExperimentProvider',
|
||||
}));
|
||||
jest.mock('containers/WidgetContainers/AppWrapper', () => 'AppWrapper');
|
||||
jest.mock('data/redux', () => ({
|
||||
selectors: 'redux.selectors',
|
||||
actions: 'redux.actions',
|
||||
@@ -49,18 +51,23 @@ describe('App router component', () => {
|
||||
const { formatMessage } = useIntl();
|
||||
describe('component', () => {
|
||||
const runBasicTests = () => {
|
||||
test('snapshot', () => { expect(el).toMatchSnapshot(); });
|
||||
test('snapshot', () => { expect(el.snapshot).toMatchSnapshot(); });
|
||||
it('displays title in helmet component', () => {
|
||||
expect(el.find(Helmet).find('title').text()).toEqual(useIntl().formatMessage(messages.pageTitle));
|
||||
const control = el.instance
|
||||
.findByType(Helmet)[0]
|
||||
.findByType('title')[0];
|
||||
expect(control.children[0].el).toEqual(formatMessage(messages.pageTitle));
|
||||
});
|
||||
it('displays learner dashboard header', () => {
|
||||
expect(el.find(LearnerDashboardHeader).length).toEqual(1);
|
||||
});
|
||||
it('wraps the page in a browser router', () => {
|
||||
expect(el.find(Router)).toMatchObject(el);
|
||||
expect(el.instance.findByType(LearnerDashboardHeader).length).toEqual(1);
|
||||
});
|
||||
test('Footer logo drawn from env variable', () => {
|
||||
expect(el.find(Footer).props().logo).toEqual(logo);
|
||||
expect(el.instance.findByType(Footer)[0].props.logo).toEqual(logo);
|
||||
});
|
||||
it('wraps the header and main components in an AppWrapper widget container', () => {
|
||||
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', () => {
|
||||
@@ -70,9 +77,14 @@ describe('App router component', () => {
|
||||
});
|
||||
runBasicTests();
|
||||
it('loads dashboard', () => {
|
||||
expect(el.find('main')).toMatchObject(shallow(
|
||||
<main><Dashboard /></main>,
|
||||
));
|
||||
const main = el.instance.findByType('main')[0];
|
||||
expect(main.children.length).toEqual(1);
|
||||
const expProvider = main.children[0];
|
||||
expect(expProvider.type).toEqual('ExperimentProvider');
|
||||
expect(expProvider.children.length).toEqual(1);
|
||||
expect(
|
||||
expProvider.matches(shallow(<ExperimentProvider><Dashboard /></ExperimentProvider>)),
|
||||
).toEqual(true);
|
||||
});
|
||||
});
|
||||
describe('initialize failure', () => {
|
||||
@@ -82,13 +94,14 @@ describe('App router component', () => {
|
||||
});
|
||||
runBasicTests();
|
||||
it('loads error page', () => {
|
||||
expect(el.find('main')).toEqual(shallow(
|
||||
<main>
|
||||
<Alert variant="danger">
|
||||
<ErrorPage message={formatMessage(messages.errorMessage, { supportEmail })} />
|
||||
</Alert>
|
||||
</main>,
|
||||
));
|
||||
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', () => {
|
||||
@@ -98,13 +111,14 @@ describe('App router component', () => {
|
||||
});
|
||||
runBasicTests();
|
||||
it('loads error page', () => {
|
||||
expect(el.find('main')).toEqual(shallow(
|
||||
<main>
|
||||
<Alert variant="danger">
|
||||
<ErrorPage message={formatMessage(messages.errorMessage, { supportEmail })} />
|
||||
</Alert>
|
||||
</main>,
|
||||
));
|
||||
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 }));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
64
src/ExperimentContext.jsx
Normal file
64
src/ExperimentContext.jsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useWindowSize, breakpoints } from '@edx/paragon';
|
||||
import { StrictDict } from 'utils';
|
||||
import api from 'widgets/ProductRecommendations/api';
|
||||
import * as module from './ExperimentContext';
|
||||
|
||||
export const state = StrictDict({
|
||||
experiment: (val) => React.useState(val), // eslint-disable-line
|
||||
countryCode: (val) => React.useState(val), // eslint-disable-line
|
||||
});
|
||||
|
||||
export const useCountryCode = (setCountryCode) => {
|
||||
React.useEffect(() => {
|
||||
api
|
||||
.fetchRecommendationsContext()
|
||||
.then((response) => {
|
||||
setCountryCode(response.data.countryCode);
|
||||
})
|
||||
.catch(() => {
|
||||
setCountryCode('');
|
||||
});
|
||||
/* eslint-disable */
|
||||
}, []);
|
||||
};
|
||||
|
||||
export const ExperimentContext = React.createContext();
|
||||
|
||||
export const ExperimentProvider = ({ children }) => {
|
||||
const [countryCode, setCountryCode] = module.state.countryCode(null);
|
||||
const [experiment, setExperiment] = module.state.experiment({
|
||||
isExperimentActive: false,
|
||||
inRecommendationsVariant: true,
|
||||
});
|
||||
|
||||
module.useCountryCode(setCountryCode);
|
||||
const { width } = useWindowSize();
|
||||
const isMobile = width < breakpoints.small.minWidth;
|
||||
|
||||
const contextValue = React.useMemo(
|
||||
() => ({
|
||||
experiment,
|
||||
countryCode,
|
||||
setExperiment,
|
||||
setCountryCode,
|
||||
isMobile,
|
||||
}),
|
||||
[experiment, countryCode, setExperiment, setCountryCode, isMobile]
|
||||
);
|
||||
|
||||
return (
|
||||
<ExperimentContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</ExperimentContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useExperimentContext = () => React.useContext(ExperimentContext);
|
||||
|
||||
ExperimentProvider.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default { useCountryCode, useExperimentContext };
|
||||
123
src/ExperimentContext.test.jsx
Normal file
123
src/ExperimentContext.test.jsx
Normal file
@@ -0,0 +1,123 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { useWindowSize } from '@edx/paragon';
|
||||
|
||||
import api from 'widgets/ProductRecommendations/api';
|
||||
import { MockUseState } from 'testUtils';
|
||||
|
||||
import * as experiment from 'ExperimentContext';
|
||||
|
||||
const state = new MockUseState(experiment);
|
||||
|
||||
jest.unmock('react');
|
||||
jest.spyOn(React, 'useEffect').mockImplementation((cb, prereqs) => ({ useEffect: { cb, prereqs } }));
|
||||
|
||||
jest.mock('widgets/ProductRecommendations/api', () => ({
|
||||
fetchRecommendationsContext: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('experiments context', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('useCountryCode', () => {
|
||||
describe('behaviour', () => {
|
||||
describe('useEffect call', () => {
|
||||
let calls;
|
||||
let cb;
|
||||
const setCountryCode = jest.fn();
|
||||
const successfulFetch = { data: { countryCode: 'ZA' } };
|
||||
|
||||
beforeEach(() => {
|
||||
experiment.useCountryCode(setCountryCode);
|
||||
|
||||
({ calls } = React.useEffect.mock);
|
||||
[[cb]] = calls;
|
||||
});
|
||||
|
||||
it('calls useEffect once', () => {
|
||||
expect(calls.length).toEqual(1);
|
||||
});
|
||||
describe('successfull fetch', () => {
|
||||
it('sets the country code', async () => {
|
||||
let resolveFn;
|
||||
api.fetchRecommendationsContext.mockReturnValueOnce(
|
||||
new Promise((resolve) => {
|
||||
resolveFn = resolve;
|
||||
}),
|
||||
);
|
||||
|
||||
cb();
|
||||
expect(api.fetchRecommendationsContext).toHaveBeenCalled();
|
||||
expect(setCountryCode).not.toHaveBeenCalled();
|
||||
resolveFn(successfulFetch);
|
||||
await waitFor(() => {
|
||||
expect(setCountryCode).toHaveBeenCalledWith(successfulFetch.data.countryCode);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('unsuccessfull fetch', () => {
|
||||
it('sets the country code to an empty string', async () => {
|
||||
let rejectFn;
|
||||
api.fetchRecommendationsContext.mockReturnValueOnce(
|
||||
new Promise((resolve, reject) => {
|
||||
rejectFn = reject;
|
||||
}),
|
||||
);
|
||||
cb();
|
||||
expect(api.fetchRecommendationsContext).toHaveBeenCalled();
|
||||
expect(setCountryCode).not.toHaveBeenCalled();
|
||||
rejectFn();
|
||||
await waitFor(() => {
|
||||
expect(setCountryCode).toHaveBeenCalledWith('');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ExperimentProvider', () => {
|
||||
const { ExperimentProvider } = experiment;
|
||||
|
||||
const TestComponent = () => {
|
||||
const {
|
||||
experiment: exp,
|
||||
setExperiment,
|
||||
countryCode,
|
||||
setCountryCode,
|
||||
isMobile,
|
||||
} = experiment.useExperimentContext();
|
||||
|
||||
expect(exp.isExperimentActive).toBeFalsy();
|
||||
expect(exp.inRecommendationsVariant).toBeTruthy();
|
||||
expect(countryCode).toBeNull();
|
||||
expect(isMobile).toBe(false);
|
||||
expect(setExperiment).toBeDefined();
|
||||
expect(setCountryCode).toBeDefined();
|
||||
|
||||
return (
|
||||
<div />
|
||||
);
|
||||
};
|
||||
|
||||
it('allows access to child components with the context stateful values', () => {
|
||||
const countryCodeSpy = jest.spyOn(experiment, 'useCountryCode').mockImplementationOnce(() => {});
|
||||
useWindowSize.mockImplementationOnce(() => ({ width: 577, height: 943 }));
|
||||
|
||||
state.mock();
|
||||
|
||||
mount(
|
||||
<ExperimentProvider>
|
||||
<TestComponent />
|
||||
</ExperimentProvider>,
|
||||
);
|
||||
|
||||
expect(countryCodeSpy).toHaveBeenCalledWith(state.setState.countryCode);
|
||||
state.expectInitializedWith(state.keys.countryCode, null);
|
||||
state.expectInitializedWith(state.keys.experiment, { isExperimentActive: false, inRecommendationsVariant: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`App router component component initialize failure snapshot 1`] = `
|
||||
<BrowserRouter>
|
||||
<Fragment>
|
||||
<HelmetWrapper
|
||||
defer={true}
|
||||
encodeSpecialCharacters={true}
|
||||
@@ -11,26 +11,28 @@ exports[`App router component component initialize failure snapshot 1`] = `
|
||||
</title>
|
||||
</HelmetWrapper>
|
||||
<div>
|
||||
<LearnerDashboardHeader />
|
||||
<main>
|
||||
<Alert
|
||||
variant="danger"
|
||||
>
|
||||
<ErrorPage
|
||||
message="If you experience repeated failures, please email support at test-support-url"
|
||||
/>
|
||||
</Alert>
|
||||
</main>
|
||||
<AppWrapper>
|
||||
<LearnerDashboardHeader />
|
||||
<main>
|
||||
<Alert
|
||||
variant="danger"
|
||||
>
|
||||
<ErrorPage
|
||||
message="If you experience repeated failures, please email support at test-support-url"
|
||||
/>
|
||||
</Alert>
|
||||
</main>
|
||||
</AppWrapper>
|
||||
<Footer
|
||||
logo="fakeLogo.png"
|
||||
/>
|
||||
<ZendeskFab />
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`App router component component no network failure snapshot 1`] = `
|
||||
<BrowserRouter>
|
||||
<Fragment>
|
||||
<HelmetWrapper
|
||||
defer={true}
|
||||
encodeSpecialCharacters={true}
|
||||
@@ -40,20 +42,24 @@ exports[`App router component component no network failure snapshot 1`] = `
|
||||
</title>
|
||||
</HelmetWrapper>
|
||||
<div>
|
||||
<LearnerDashboardHeader />
|
||||
<main>
|
||||
<Dashboard />
|
||||
</main>
|
||||
<AppWrapper>
|
||||
<LearnerDashboardHeader />
|
||||
<main>
|
||||
<ExperimentProvider>
|
||||
<Dashboard />
|
||||
</ExperimentProvider>
|
||||
</main>
|
||||
</AppWrapper>
|
||||
<Footer
|
||||
logo="fakeLogo.png"
|
||||
/>
|
||||
<ZendeskFab />
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`App router component component refresh failure snapshot 1`] = `
|
||||
<BrowserRouter>
|
||||
<Fragment>
|
||||
<HelmetWrapper
|
||||
defer={true}
|
||||
encodeSpecialCharacters={true}
|
||||
@@ -63,20 +69,22 @@ exports[`App router component component refresh failure snapshot 1`] = `
|
||||
</title>
|
||||
</HelmetWrapper>
|
||||
<div>
|
||||
<LearnerDashboardHeader />
|
||||
<main>
|
||||
<Alert
|
||||
variant="danger"
|
||||
>
|
||||
<ErrorPage
|
||||
message="If you experience repeated failures, please email support at test-support-url"
|
||||
/>
|
||||
</Alert>
|
||||
</main>
|
||||
<AppWrapper>
|
||||
<LearnerDashboardHeader />
|
||||
<main>
|
||||
<Alert
|
||||
variant="danger"
|
||||
>
|
||||
<ErrorPage
|
||||
message="If you experience repeated failures, please email support at test-support-url"
|
||||
/>
|
||||
</Alert>
|
||||
</main>
|
||||
</AppWrapper>
|
||||
<Footer
|
||||
logo="fakeLogo.png"
|
||||
/>
|
||||
<ZendeskFab />
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
@@ -13,18 +13,28 @@ exports[`app registry subscribe: APP_READY. links App to root element 1`] = `
|
||||
"redux": "store",
|
||||
}
|
||||
}
|
||||
wrapWithRouter={true}
|
||||
>
|
||||
<NoticesWrapper>
|
||||
<Switch>
|
||||
<PageRoute
|
||||
<Routes>
|
||||
<Route
|
||||
element={
|
||||
<PageWrap>
|
||||
<App />
|
||||
</PageWrap>
|
||||
}
|
||||
path="/"
|
||||
>
|
||||
<App />
|
||||
</PageRoute>
|
||||
<Redirect
|
||||
to="/"
|
||||
/>
|
||||
</Switch>
|
||||
<Route
|
||||
element={
|
||||
<Navigate
|
||||
replace={true}
|
||||
to="/"
|
||||
/>
|
||||
}
|
||||
path="*"
|
||||
/>
|
||||
</Routes>
|
||||
</NoticesWrapper>
|
||||
</AppProvider>
|
||||
`;
|
||||
|
||||
@@ -16,6 +16,7 @@ const configuration = {
|
||||
SUPPORT_URL: process.env.SUPPORT_URL || null,
|
||||
ENABLE_NOTICES: process.env.ENABLE_NOTICES || null,
|
||||
CAREER_LINK_URL: process.env.CAREER_LINK_URL || null,
|
||||
LOGO_URL: process.env.LOGO_URL,
|
||||
};
|
||||
|
||||
const features = {};
|
||||
|
||||
29
src/containers/CertificatePreviewModal/hooks.js
Normal file
29
src/containers/CertificatePreviewModal/hooks.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import messages from './messages';
|
||||
|
||||
export const useCertificatePreviewData = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const selectedCardId = reduxHooks.useCertificatePreviewData().cardId;
|
||||
|
||||
const { courseId } = reduxHooks.useCardCourseRunData(selectedCardId) || {};
|
||||
|
||||
const courseTitle = courseId;
|
||||
const header = formatMessage(messages.previewTitle, { courseTitle });
|
||||
|
||||
const closePreviewModal = reduxHooks.useUpdateCertificatePreviewModalCallback(null);
|
||||
|
||||
const getCertificatePreviewUrl = () => `${getConfig().LMS_BASE_URL}/certificates/upsell/course/${courseId}`;
|
||||
|
||||
return {
|
||||
showModal: selectedCardId != null,
|
||||
header,
|
||||
closePreviewModal,
|
||||
getCertificatePreviewUrl,
|
||||
};
|
||||
};
|
||||
|
||||
export default useCertificatePreviewData;
|
||||
48
src/containers/CertificatePreviewModal/index.jsx
Normal file
48
src/containers/CertificatePreviewModal/index.jsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
ModalDialog,
|
||||
} from '@edx/paragon';
|
||||
import { UpgradeButton } from '../CourseCard/components/CourseCardActions/UpgradeButton';
|
||||
import useCertificatePreviewData from './hooks';
|
||||
|
||||
export const CertificatePreviewModal = ({
|
||||
cardId,
|
||||
}) => {
|
||||
const {
|
||||
showModal,
|
||||
header,
|
||||
closePreviewModal,
|
||||
getCertificatePreviewUrl,
|
||||
} = useCertificatePreviewData();
|
||||
|
||||
return (
|
||||
<ModalDialog
|
||||
isOpen={showModal}
|
||||
onClose={closePreviewModal}
|
||||
hasCloseButton
|
||||
isFullscreenOnMobile
|
||||
size="lg"
|
||||
className="p-4 px-4.5"
|
||||
title={header}
|
||||
>
|
||||
<h3>{header}</h3>
|
||||
<div>
|
||||
<iframe
|
||||
title={header}
|
||||
src={getCertificatePreviewUrl()}
|
||||
width={725}
|
||||
height={400}
|
||||
/>
|
||||
</div>
|
||||
<UpgradeButton
|
||||
cardId={cardId}
|
||||
/>
|
||||
</ModalDialog>
|
||||
);
|
||||
};
|
||||
CertificatePreviewModal.propTypes = {
|
||||
cardId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default CertificatePreviewModal;
|
||||
12
src/containers/CertificatePreviewModal/messages.js
Normal file
12
src/containers/CertificatePreviewModal/messages.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/* eslint-disable quotes */
|
||||
import { StrictDict } from 'utils';
|
||||
|
||||
export const messages = StrictDict({
|
||||
previewTitle: {
|
||||
id: 'learner-dash.certificatePreview.title',
|
||||
description: 'The title of the email settings modal',
|
||||
defaultMessage: 'Your certificate preview for {courseTitle} ',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -12,11 +12,13 @@ import messages from './messages';
|
||||
export const BeginCourseButton = ({ cardId }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { homeUrl } = reduxHooks.useCardCourseRunData(cardId);
|
||||
const execEdTrackingParam = reduxHooks.useCardExecEdTrackingParam(cardId);
|
||||
const { disableBeginCourse } = useActionDisabledState(cardId);
|
||||
|
||||
const handleClick = reduxHooks.useTrackCourseEvent(
|
||||
track.course.enterCourseClicked,
|
||||
cardId,
|
||||
homeUrl,
|
||||
homeUrl + execEdTrackingParam,
|
||||
);
|
||||
return (
|
||||
<ActionButton
|
||||
|
||||
@@ -14,17 +14,22 @@ jest.mock('tracking', () => ({
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCourseRunData: jest.fn(() => ({ homeUrl: 'home-url' })),
|
||||
useTrackCourseEvent: jest.fn(
|
||||
(eventName, cardId, upgradeUrl) => ({ trackCourseEvent: { eventName, cardId, upgradeUrl } }),
|
||||
),
|
||||
useCardCourseRunData: jest.fn(),
|
||||
useCardExecEdTrackingParam: jest.fn(),
|
||||
useTrackCourseEvent: jest.fn(),
|
||||
},
|
||||
}));
|
||||
jest.mock('../hooks', () => jest.fn(() => ({ disableBeginCourse: false })));
|
||||
jest.mock('./ActionButton', () => 'ActionButton');
|
||||
|
||||
let wrapper;
|
||||
const { homeUrl } = reduxHooks.useCardCourseRunData();
|
||||
const homeUrl = 'home-url';
|
||||
reduxHooks.useCardCourseRunData.mockReturnValue({ homeUrl });
|
||||
const execEdPath = (cardId) => `exec-ed-tracking-path=${cardId}`;
|
||||
reduxHooks.useCardExecEdTrackingParam.mockImplementation(execEdPath);
|
||||
reduxHooks.useTrackCourseEvent.mockImplementation(
|
||||
(eventName, cardId, upgradeUrl) => ({ trackCourseEvent: { eventName, cardId, upgradeUrl } }),
|
||||
);
|
||||
|
||||
describe('BeginCourseButton', () => {
|
||||
const props = {
|
||||
@@ -33,27 +38,50 @@ describe('BeginCourseButton', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
describe('snapshot', () => {
|
||||
test('renders default button when learner has access to the course', () => {
|
||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(false);
|
||||
expect(wrapper.prop(htmlProps.onClick)).toEqual(reduxHooks.useTrackCourseEvent(
|
||||
track.course.enterCourseClicked,
|
||||
props.cardId,
|
||||
homeUrl,
|
||||
));
|
||||
});
|
||||
});
|
||||
describe('behavior', () => {
|
||||
it('initializes course run data with cardId', () => {
|
||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
||||
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(props.cardId);
|
||||
});
|
||||
test('disabled states', () => {
|
||||
useActionDisabledState.mockReturnValueOnce({ disableBeginCourse: true });
|
||||
it('loads exec education path param', () => {
|
||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
|
||||
expect(reduxHooks.useCardExecEdTrackingParam).toHaveBeenCalledWith(props.cardId);
|
||||
});
|
||||
it('loads disabled states for begin action from action hooks', () => {
|
||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
||||
expect(useActionDisabledState).toHaveBeenCalledWith(props.cardId);
|
||||
});
|
||||
});
|
||||
describe('snapshot', () => {
|
||||
describe('disabled', () => {
|
||||
beforeEach(() => {
|
||||
useActionDisabledState.mockReturnValueOnce({ disableBeginCourse: true });
|
||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
||||
});
|
||||
test('snapshot', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it('should be disabled', () => {
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
|
||||
});
|
||||
});
|
||||
describe('enabled', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
||||
});
|
||||
test('snapshot', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it('should be enabled', () => {
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(false);
|
||||
});
|
||||
it('should track enter course clicked event on click, with exec ed param', () => {
|
||||
expect(wrapper.prop(htmlProps.onClick)).toEqual(reduxHooks.useTrackCourseEvent(
|
||||
track.course.enterCourseClicked,
|
||||
props.cardId,
|
||||
homeUrl + execEdPath(props.cardId),
|
||||
));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,11 +12,13 @@ import messages from './messages';
|
||||
export const ResumeButton = ({ cardId }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { resumeUrl } = reduxHooks.useCardCourseRunData(cardId);
|
||||
const execEdTrackingParam = reduxHooks.useCardExecEdTrackingParam(cardId);
|
||||
const { disableResumeCourse } = useActionDisabledState(cardId);
|
||||
|
||||
const handleClick = reduxHooks.useTrackCourseEvent(
|
||||
track.course.enterCourseClicked,
|
||||
cardId,
|
||||
resumeUrl,
|
||||
resumeUrl + execEdTrackingParam,
|
||||
);
|
||||
return (
|
||||
<ActionButton
|
||||
|
||||
@@ -6,52 +6,80 @@ import track from 'tracking';
|
||||
import useActionDisabledState from '../hooks';
|
||||
import ResumeButton from './ResumeButton';
|
||||
|
||||
jest.mock('tracking', () => ({
|
||||
course: {
|
||||
enterCourseClicked: jest.fn().mockName('segment.enterCourseClicked'),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCourseRunData: jest.fn(() => ({ resumeUrl: 'resumeUrl' })),
|
||||
useTrackCourseEvent: (eventName, cardId, url) => jest
|
||||
.fn()
|
||||
.mockName(`useTrackCourseEvent('${eventName}', '${cardId}', '${url}')`),
|
||||
useCardCourseRunData: jest.fn(),
|
||||
useCardExecEdTrackingParam: jest.fn(),
|
||||
useTrackCourseEvent: jest.fn(),
|
||||
},
|
||||
}));
|
||||
jest.mock('../hooks', () => jest.fn(() => ({ disableResumeCourse: false })));
|
||||
jest.mock('tracking', () => ({
|
||||
course: {
|
||||
enterCourseClicked: 'enterCourseClicked',
|
||||
},
|
||||
}));
|
||||
jest.mock('./ActionButton', () => 'ActionButton');
|
||||
|
||||
const { resumeUrl } = reduxHooks.useCardCourseRunData();
|
||||
const resumeUrl = 'resume-url';
|
||||
reduxHooks.useCardCourseRunData.mockReturnValue({ resumeUrl });
|
||||
const execEdPath = (cardId) => `exec-ed-tracking-path=${cardId}`;
|
||||
reduxHooks.useCardExecEdTrackingParam.mockImplementation(execEdPath);
|
||||
reduxHooks.useTrackCourseEvent.mockImplementation(
|
||||
(eventName, cardId, upgradeUrl) => ({ trackCourseEvent: { eventName, cardId, upgradeUrl } }),
|
||||
);
|
||||
|
||||
let wrapper;
|
||||
|
||||
describe('ResumeButton', () => {
|
||||
const props = {
|
||||
cardId: 'cardId',
|
||||
};
|
||||
describe('snapshot', () => {
|
||||
test('renders default button when learner has access to the course', () => {
|
||||
const wrapper = shallow(<ResumeButton {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(false);
|
||||
expect(wrapper.prop(htmlProps.onClick).getMockName()).toContain(
|
||||
'useTrackCourseEvent',
|
||||
track.course.enterCourseClicked,
|
||||
props.cardId,
|
||||
resumeUrl,
|
||||
);
|
||||
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', () => {
|
||||
it('initializes course run data based on cardId', () => {
|
||||
shallow(<ResumeButton {...props} />);
|
||||
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(
|
||||
props.cardId,
|
||||
);
|
||||
describe('snapshot', () => {
|
||||
describe('disabled', () => {
|
||||
beforeEach(() => {
|
||||
useActionDisabledState.mockReturnValueOnce({ disableResumeCourse: true });
|
||||
wrapper = shallow(<ResumeButton {...props} />);
|
||||
});
|
||||
test('snapshot', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it('should be disabled', () => {
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
|
||||
});
|
||||
});
|
||||
test('disabled states', () => {
|
||||
useActionDisabledState.mockReturnValueOnce({ disableResumeCourse: true });
|
||||
const wrapper = shallow(<ResumeButton {...props} />);
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
|
||||
describe('enabled', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<ResumeButton {...props} />);
|
||||
});
|
||||
test('snapshot', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it('should be enabled', () => {
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(false);
|
||||
});
|
||||
it('should track enter course clicked event on click, with exec ed param', () => {
|
||||
expect(wrapper.prop(htmlProps.onClick)).toEqual(reduxHooks.useTrackCourseEvent(
|
||||
track.course.enterCourseClicked,
|
||||
props.cardId,
|
||||
resumeUrl + execEdPath(props.cardId),
|
||||
));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`BeginCourseButton snapshot renders default button when learner has access to the course 1`] = `
|
||||
exports[`BeginCourseButton snapshot disabled snapshot 1`] = `
|
||||
<ActionButton
|
||||
as="a"
|
||||
disabled={true}
|
||||
href="#"
|
||||
onClick={
|
||||
Object {
|
||||
"trackCourseEvent": Object {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.enterCourseClicked],
|
||||
"upgradeUrl": "home-urlexec-ed-tracking-path=cardId",
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
Begin Course
|
||||
</ActionButton>
|
||||
`;
|
||||
|
||||
exports[`BeginCourseButton snapshot enabled snapshot 1`] = `
|
||||
<ActionButton
|
||||
as="a"
|
||||
disabled={false}
|
||||
@@ -10,7 +29,7 @@ exports[`BeginCourseButton snapshot renders default button when learner has acce
|
||||
"trackCourseEvent": Object {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.enterCourseClicked],
|
||||
"upgradeUrl": "home-url",
|
||||
"upgradeUrl": "home-urlexec-ed-tracking-path=cardId",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,38 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ResumeButton snapshot renders default button when learner has access to the course 1`] = `
|
||||
exports[`ResumeButton snapshot disabled snapshot 1`] = `
|
||||
<ActionButton
|
||||
as="a"
|
||||
disabled={false}
|
||||
disabled={true}
|
||||
href="#"
|
||||
onClick={[MockFunction useTrackCourseEvent('enterCourseClicked', 'cardId', 'resumeUrl')]}
|
||||
onClick={
|
||||
Object {
|
||||
"trackCourseEvent": Object {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.enterCourseClicked],
|
||||
"upgradeUrl": "resume-urlexec-ed-tracking-path=cardId",
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
Resume
|
||||
</ActionButton>
|
||||
`;
|
||||
|
||||
exports[`ResumeButton snapshot enabled snapshot 1`] = `
|
||||
<ActionButton
|
||||
as="a"
|
||||
disabled={false}
|
||||
href="#"
|
||||
onClick={
|
||||
Object {
|
||||
"trackCourseEvent": Object {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.enterCourseClicked],
|
||||
"upgradeUrl": "resume-urlexec-ed-tracking-path=cardId",
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
Resume
|
||||
</ActionButton>
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CourseCardActions snapshot show begin course button when verified and not entitlement and has started 1`] = `
|
||||
<ActionRow
|
||||
data-test-id="CourseCardActions"
|
||||
>
|
||||
<BeginCourseButton
|
||||
cardId="cardId"
|
||||
/>
|
||||
</ActionRow>
|
||||
`;
|
||||
|
||||
exports[`CourseCardActions snapshot show resume button when verified and not entitlement and has started 1`] = `
|
||||
<ActionRow
|
||||
data-test-id="CourseCardActions"
|
||||
>
|
||||
<ResumeButton
|
||||
cardId="cardId"
|
||||
/>
|
||||
</ActionRow>
|
||||
`;
|
||||
|
||||
exports[`CourseCardActions snapshot show select session button when not verified and entitlement 1`] = `
|
||||
<ActionRow
|
||||
data-test-id="CourseCardActions"
|
||||
>
|
||||
<SelectSessionButton
|
||||
cardId="cardId"
|
||||
/>
|
||||
</ActionRow>
|
||||
`;
|
||||
|
||||
exports[`CourseCardActions snapshot show upgrade button when not verified and not entitlement 1`] = `
|
||||
<ActionRow
|
||||
data-test-id="CourseCardActions"
|
||||
>
|
||||
<UpgradeButton
|
||||
cardId="cardId"
|
||||
/>
|
||||
<BeginCourseButton
|
||||
cardId="cardId"
|
||||
/>
|
||||
</ActionRow>
|
||||
`;
|
||||
|
||||
exports[`CourseCardActions snapshot show view course button when not verified and entitlement and fulfilled 1`] = `
|
||||
<ActionRow
|
||||
data-test-id="CourseCardActions"
|
||||
>
|
||||
<ViewCourseButton
|
||||
cardId="cardId"
|
||||
/>
|
||||
</ActionRow>
|
||||
`;
|
||||
@@ -13,21 +13,27 @@ import ViewCourseButton from './ViewCourseButton';
|
||||
|
||||
export const CourseCardActions = ({ cardId }) => {
|
||||
const { isEntitlement, isFulfilled } = reduxHooks.useCardEntitlementData(cardId);
|
||||
const { isVerified, hasStarted } = reduxHooks.useCardEnrollmentData(cardId);
|
||||
const {
|
||||
isVerified,
|
||||
hasStarted,
|
||||
isExecEd2UCourse,
|
||||
} = reduxHooks.useCardEnrollmentData(cardId);
|
||||
const { isArchived } = reduxHooks.useCardCourseRunData(cardId);
|
||||
let PrimaryButton;
|
||||
if (isEntitlement) {
|
||||
PrimaryButton = isFulfilled ? ViewCourseButton : SelectSessionButton;
|
||||
} else if (isArchived) {
|
||||
PrimaryButton = ViewCourseButton;
|
||||
} else {
|
||||
PrimaryButton = hasStarted ? ResumeButton : BeginCourseButton;
|
||||
}
|
||||
|
||||
return (
|
||||
<ActionRow data-test-id="CourseCardActions">
|
||||
{!(isEntitlement || isVerified) && <UpgradeButton cardId={cardId} />}
|
||||
<PrimaryButton cardId={cardId} />
|
||||
{!(isEntitlement || isVerified || isExecEd2UCourse) && <UpgradeButton cardId={cardId} />}
|
||||
{isEntitlement && (isFulfilled
|
||||
? <ViewCourseButton cardId={cardId} />
|
||||
: <SelectSessionButton cardId={cardId} />
|
||||
)}
|
||||
{(isArchived && !isEntitlement) && (
|
||||
<ViewCourseButton cardId={cardId} />
|
||||
)}
|
||||
{!(isArchived || isEntitlement) && (hasStarted
|
||||
? <ResumeButton cardId={cardId} />
|
||||
: <BeginCourseButton cardId={cardId} />
|
||||
)}
|
||||
</ActionRow>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { shallow } from 'enzyme';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import UpgradeButton from './UpgradeButton';
|
||||
import SelectSessionButton from './SelectSessionButton';
|
||||
import BeginCourseButton from './BeginCourseButton';
|
||||
import ResumeButton from './ResumeButton';
|
||||
import ViewCourseButton from './ViewCourseButton';
|
||||
|
||||
import CourseCardActions from '.';
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
@@ -9,6 +15,7 @@ jest.mock('hooks', () => ({
|
||||
useCardCourseRunData: jest.fn(),
|
||||
useCardEnrollmentData: jest.fn(),
|
||||
useCardEntitlementData: jest.fn(),
|
||||
useMasqueradeData: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -18,87 +25,92 @@ 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 props = {
|
||||
cardId: 'cardId',
|
||||
};
|
||||
const createWrapper = ({
|
||||
isEntitlement, isFulfilled, isArchived, isVerified, hasStarted,
|
||||
}) => {
|
||||
const mockHooks = ({
|
||||
isEntitlement = false,
|
||||
isExecEd2UCourse = false,
|
||||
isFulfilled = false,
|
||||
isArchived = false,
|
||||
isVerified = false,
|
||||
hasStarted = false,
|
||||
isMasquerading = false,
|
||||
} = {}) => {
|
||||
reduxHooks.useCardEntitlementData.mockReturnValueOnce({ isEntitlement, isFulfilled });
|
||||
reduxHooks.useCardCourseRunData.mockReturnValueOnce({ isArchived });
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ isVerified, hasStarted });
|
||||
return shallow(<CourseCardActions {...props} />);
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ isExecEd2UCourse, isVerified, hasStarted });
|
||||
reduxHooks.useMasqueradeData.mockReturnValueOnce({ isMasquerading });
|
||||
};
|
||||
describe('snapshot', () => {
|
||||
test('show upgrade button when not verified and not entitlement', () => {
|
||||
const wrapper = createWrapper({
|
||||
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: false, hasStarted: false,
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
test('show select session button when not verified and entitlement', () => {
|
||||
const wrapper = createWrapper({
|
||||
isEntitlement: true, isFulfilled: false, isArchived: false, isVerified: false, hasStarted: false,
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
test('show begin course button when verified and not entitlement and has started', () => {
|
||||
const wrapper = createWrapper({
|
||||
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: true, hasStarted: false,
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
test('show resume button when verified and not entitlement and has started', () => {
|
||||
const wrapper = createWrapper({
|
||||
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: true, hasStarted: true,
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
test('show view course button when not verified and entitlement and fulfilled', () => {
|
||||
const wrapper = createWrapper({
|
||||
isEntitlement: true, isFulfilled: true, isArchived: false, isVerified: false, hasStarted: false,
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
const render = () => {
|
||||
el = shallow(<CourseCardActions {...props} />);
|
||||
};
|
||||
describe('behavior', () => {
|
||||
it('initializes redux hooks', () => {
|
||||
mockHooks();
|
||||
render();
|
||||
expect(reduxHooks.useCardEntitlementData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useCardEnrollmentData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('behavior', () => {
|
||||
it('show upgrade button when not verified and not entitlement', () => {
|
||||
const wrapper = createWrapper({
|
||||
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: false, hasStarted: false,
|
||||
describe('output', () => {
|
||||
describe('Exec Ed course', () => {
|
||||
it('does not render upgrade button', () => {
|
||||
mockHooks({ isExecEd2UCourse: true });
|
||||
render();
|
||||
expect(el.instance.findByType(UpgradeButton).length).toEqual(0);
|
||||
});
|
||||
expect(wrapper.find('UpgradeButton')).toHaveLength(1);
|
||||
});
|
||||
it('show select session button when not verified and entitlement', () => {
|
||||
const wrapper = createWrapper({
|
||||
isEntitlement: true, isFulfilled: false, isArchived: false, isVerified: false, hasStarted: false,
|
||||
describe('entitlement course', () => {
|
||||
it('does not render upgrade button', () => {
|
||||
mockHooks({ isEntitlement: true });
|
||||
render();
|
||||
expect(el.instance.findByType(UpgradeButton).length).toEqual(0);
|
||||
});
|
||||
it('renders ViewCourseButton if fulfilled', () => {
|
||||
mockHooks({ isEntitlement: true, isFulfilled: true });
|
||||
render();
|
||||
expect(el.instance.findByType(ViewCourseButton)[0].props.cardId).toEqual(cardId);
|
||||
});
|
||||
it('renders SelectSessionButton if not fulfilled', () => {
|
||||
mockHooks({ isEntitlement: true });
|
||||
render();
|
||||
expect(el.instance.findByType(SelectSessionButton)[0].props.cardId).toEqual(cardId);
|
||||
});
|
||||
expect(wrapper.find('SelectSessionButton')).toHaveLength(1);
|
||||
});
|
||||
it('show begin course button when verified and not entitlement and has started', () => {
|
||||
const wrapper = createWrapper({
|
||||
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: true, hasStarted: false,
|
||||
describe('verified course', () => {
|
||||
it('does not render upgrade button', () => {
|
||||
mockHooks({ isVerified: true });
|
||||
render();
|
||||
expect(el.instance.findByType(UpgradeButton).length).toEqual(0);
|
||||
});
|
||||
expect(wrapper.find('BeginCourseButton')).toHaveLength(1);
|
||||
});
|
||||
it('show resume button when verified and not entitlement and has started', () => {
|
||||
const wrapper = createWrapper({
|
||||
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: true, hasStarted: true,
|
||||
describe('not entielement, verified, or exec ed', () => {
|
||||
it('renders UpgradeButton and ViewCourseButton for archived courses', () => {
|
||||
mockHooks({ isArchived: true });
|
||||
render();
|
||||
expect(el.instance.findByType(UpgradeButton)[0].props.cardId).toEqual(cardId);
|
||||
expect(el.instance.findByType(ViewCourseButton)[0].props.cardId).toEqual(cardId);
|
||||
});
|
||||
expect(wrapper.find('ResumeButton')).toHaveLength(1);
|
||||
});
|
||||
it('show view course button when not verified and entitlement and fulfilled', () => {
|
||||
const wrapper = createWrapper({
|
||||
isEntitlement: true, isFulfilled: true, isArchived: false, isVerified: false, hasStarted: false,
|
||||
describe('unstarted courses', () => {
|
||||
it('renders UpgradeButton and BeginCourseButton', () => {
|
||||
mockHooks();
|
||||
render();
|
||||
expect(el.instance.findByType(UpgradeButton)[0].props.cardId).toEqual(cardId);
|
||||
expect(el.instance.findByType(BeginCourseButton)[0].props.cardId).toEqual(cardId);
|
||||
});
|
||||
});
|
||||
expect(wrapper.find('ViewCourseButton')).toHaveLength(1);
|
||||
});
|
||||
it('show view course button when not verified and entitlement and fulfilled and archived', () => {
|
||||
const wrapper = createWrapper({
|
||||
isEntitlement: true, isFulfilled: true, isArchived: true, isVerified: false, hasStarted: false,
|
||||
describe('active courses (started, and not archived)', () => {
|
||||
it('renders UpgradeButton and ResumeButton', () => {
|
||||
mockHooks({ hasStarted: true });
|
||||
render();
|
||||
expect(el.instance.findByType(UpgradeButton)[0].props.cardId).toEqual(cardId);
|
||||
expect(el.instance.findByType(ResumeButton)[0].props.cardId).toEqual(cardId);
|
||||
});
|
||||
});
|
||||
expect(wrapper.find('ViewCourseButton')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,6 +47,7 @@ export const useCardDetailsData = ({ cardId }) => {
|
||||
} = reduxHooks.useCardEntitlementData(cardId);
|
||||
|
||||
const openSessionModal = reduxHooks.useUpdateSelectSessionModalCallback(cardId);
|
||||
const openCertificatePreview = reduxHooks.useUpdateCertificatePreviewModalCallback(cardId);
|
||||
|
||||
return {
|
||||
providerName: providerName || formatMessage(messages.unknownProviderName),
|
||||
@@ -57,6 +58,7 @@ export const useCardDetailsData = ({ cardId }) => {
|
||||
openSessionModal,
|
||||
courseNumber,
|
||||
changeOrLeaveSessionMessage: formatMessage(messages.changeOrLeaveSessionButton),
|
||||
openCertificatePreview,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -16,23 +16,35 @@ export const CourseCardDetails = ({ cardId }) => {
|
||||
openSessionModal,
|
||||
courseNumber,
|
||||
changeOrLeaveSessionMessage,
|
||||
openCertificatePreview,
|
||||
} = useCardDetailsData({ cardId });
|
||||
|
||||
return (
|
||||
<span className="small" data-testid="CourseCardDetails">
|
||||
{providerName} • {courseNumber}
|
||||
{!(isEntitlement && !isFulfilled) && accessMessage && (
|
||||
` • ${accessMessage}`
|
||||
)}
|
||||
{isEntitlement && isFulfilled && canChange ? (
|
||||
<>
|
||||
{' • '}
|
||||
<Button variant="link" size="inline" className="m-0 p-0" onClick={openSessionModal}>
|
||||
{changeOrLeaveSessionMessage}
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
</span>
|
||||
<div>
|
||||
<span className="small" data-testid="CourseCardDetails">
|
||||
{providerName} • {courseNumber}
|
||||
{!(isEntitlement && !isFulfilled) && accessMessage && (
|
||||
` • ${accessMessage}`
|
||||
)}
|
||||
{isEntitlement && isFulfilled && canChange ? (
|
||||
<>
|
||||
{' • '}
|
||||
<Button variant="link" size="inline" className="m-0 p-0" onClick={openSessionModal}>
|
||||
{changeOrLeaveSessionMessage}
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
</span>
|
||||
<Button
|
||||
variant="link"
|
||||
size="inline"
|
||||
className="float-right"
|
||||
data-testid="certificate-preview"
|
||||
onClick={openCertificatePreview}
|
||||
>
|
||||
Preview Your Certificate
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
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 '@edx/paragon';
|
||||
|
||||
import track from 'tracking';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
export const testIds = StrictDict({
|
||||
emailSettingsModalToggle: 'emailSettingsModalToggle',
|
||||
});
|
||||
|
||||
export const SocialShareMenu = ({ cardId, emailSettings }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const { courseName } = reduxHooks.useCardCourseData(cardId);
|
||||
const { isEmailEnabled, isExecEd2UCourse } = reduxHooks.useCardEnrollmentData(cardId);
|
||||
const { twitter, facebook } = reduxHooks.useCardSocialSettingsData(cardId);
|
||||
const { isMasquerading } = reduxHooks.useMasqueradeData();
|
||||
|
||||
const handleTwitterShare = reduxHooks.useTrackCourseEvent(track.socialShare, cardId, 'twitter');
|
||||
const handleFacebookShare = reduxHooks.useTrackCourseEvent(track.socialShare, cardId, 'facebook');
|
||||
|
||||
if (isExecEd2UCourse) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{isEmailEnabled && (
|
||||
<Dropdown.Item
|
||||
disabled={isMasquerading}
|
||||
onClick={emailSettings.show}
|
||||
data-testid={testIds.emailSettingsModalToggle}
|
||||
>
|
||||
{formatMessage(messages.emailSettings)}
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
{facebook.isEnabled && (
|
||||
<ReactShare.FacebookShareButton
|
||||
url={facebook.shareUrl}
|
||||
onClick={handleFacebookShare}
|
||||
title={formatMessage(messages.shareQuote, {
|
||||
courseName,
|
||||
socialBrand: facebook.socialBrand,
|
||||
})}
|
||||
resetButtonStyle={false}
|
||||
className="pgn__dropdown-item dropdown-item"
|
||||
>
|
||||
{formatMessage(messages.shareToFacebook)}
|
||||
</ReactShare.FacebookShareButton>
|
||||
)}
|
||||
{twitter.isEnabled && (
|
||||
<ReactShare.TwitterShareButton
|
||||
url={twitter.shareUrl}
|
||||
onClick={handleTwitterShare}
|
||||
title={formatMessage(messages.shareQuote, {
|
||||
courseName,
|
||||
socialBrand: twitter.socialBrand,
|
||||
})}
|
||||
resetButtonStyle={false}
|
||||
className="pgn__dropdown-item dropdown-item"
|
||||
>
|
||||
{formatMessage(messages.shareToTwitter)}
|
||||
</ReactShare.TwitterShareButton>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
SocialShareMenu.propTypes = {
|
||||
cardId: PropTypes.string.isRequired,
|
||||
emailSettings: PropTypes.shape({
|
||||
show: PropTypes.func,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default SocialShareMenu;
|
||||
@@ -0,0 +1,235 @@
|
||||
import { when } from 'jest-when';
|
||||
import * as ReactShare from 'react-share';
|
||||
|
||||
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, { 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', () => ({
|
||||
useIntl: jest.fn().mockReturnValue({
|
||||
formatMessage: jest.requireActual('@edx/react-unit-test-utils').formatMessage,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useMasqueradeData: jest.fn(),
|
||||
useCardCourseData: jest.fn(),
|
||||
useCardEnrollmentData: jest.fn(),
|
||||
useCardSocialSettingsData: jest.fn(),
|
||||
useTrackCourseEvent: jest.fn((...args) => ({ trackCourseEvent: args })),
|
||||
},
|
||||
}));
|
||||
jest.mock('./hooks', () => ({
|
||||
useEmailSettings: jest.fn(),
|
||||
}));
|
||||
|
||||
const props = {
|
||||
cardId: 'test-card-id',
|
||||
emailSettings: { show: jest.fn() },
|
||||
};
|
||||
|
||||
const mockHook = (fn, returnValue, options = {}) => {
|
||||
if (options.isCardHook) {
|
||||
when(fn).calledWith(props.cardId).mockReturnValueOnce(returnValue);
|
||||
} else {
|
||||
when(fn).calledWith().mockReturnValueOnce(returnValue);
|
||||
}
|
||||
};
|
||||
|
||||
const courseName = 'test-course-name';
|
||||
|
||||
const socialShare = {
|
||||
facebook: {
|
||||
isEnabled: true,
|
||||
shareUrl: 'facebook-share-url',
|
||||
socialBrand: 'facebook-social-brand',
|
||||
},
|
||||
twitter: {
|
||||
isEnabled: true,
|
||||
shareUrl: 'twitter-share-url',
|
||||
socialBrand: 'twitter-social-brand',
|
||||
},
|
||||
};
|
||||
|
||||
const mockHooks = (returnVals = {}) => {
|
||||
mockHook(
|
||||
reduxHooks.useCardEnrollmentData,
|
||||
{
|
||||
isEmailEnabled: !!returnVals.isEmailEnabled,
|
||||
isExecEd2UCourse: !!returnVals.isExecEd2UCourse,
|
||||
},
|
||||
{ isCardHook: true },
|
||||
);
|
||||
mockHook(reduxHooks.useCardCourseData, { courseName }, { isCardHook: true });
|
||||
mockHook(reduxHooks.useMasqueradeData, { isMasquerading: !!returnVals.isMasquerading });
|
||||
mockHook(
|
||||
reduxHooks.useCardSocialSettingsData,
|
||||
{
|
||||
facebook: { ...socialShare.facebook, isEnabled: !!returnVals.facebook?.isEnabled },
|
||||
twitter: { ...socialShare.twitter, isEnabled: !!returnVals.twitter?.isEnabled },
|
||||
},
|
||||
{ isCardHook: true },
|
||||
);
|
||||
};
|
||||
|
||||
let el;
|
||||
const render = () => {
|
||||
el = shallow(<SocialShareMenu {...props} />);
|
||||
};
|
||||
|
||||
describe('SocialShareMenu', () => {
|
||||
describe('behavior', () => {
|
||||
beforeEach(() => {
|
||||
mockHooks();
|
||||
render();
|
||||
});
|
||||
it('initializes intl hook', () => {
|
||||
expect(useIntl).toHaveBeenCalledWith();
|
||||
});
|
||||
it('initializes local hooks', () => {
|
||||
when(useEmailSettings).expectCalledWith();
|
||||
});
|
||||
it('initializes redux hook data ', () => {
|
||||
when(reduxHooks.useCardEnrollmentData).expectCalledWith(props.cardId);
|
||||
when(reduxHooks.useCardCourseData).expectCalledWith(props.cardId);
|
||||
when(reduxHooks.useCardSocialSettingsData).expectCalledWith(props.cardId);
|
||||
when(reduxHooks.useMasqueradeData).expectCalledWith();
|
||||
when(reduxHooks.useTrackCourseEvent).expectCalledWith(track.socialShare, props.cardId, 'twitter');
|
||||
when(reduxHooks.useTrackCourseEvent).expectCalledWith(track.socialShare, props.cardId, 'facebook');
|
||||
});
|
||||
});
|
||||
describe('render', () => {
|
||||
it('renders null if exec ed course', () => {
|
||||
mockHooks({ isExecEd2UCourse: true });
|
||||
render();
|
||||
expect(el.isEmptyRender()).toEqual(true);
|
||||
});
|
||||
const testEmailSettingsDropdown = (isMasquerading = false) => {
|
||||
describe('email settings dropdown', () => {
|
||||
const loadToggle = () => el.instance.findByTestId(testIds.emailSettingsModalToggle)[0];
|
||||
it('renders', () => {
|
||||
expect(el.instance.findByTestId(testIds.emailSettingsModalToggle).length).toEqual(1);
|
||||
});
|
||||
if (isMasquerading) {
|
||||
it('is disabled', () => {
|
||||
expect(loadToggle().props.disabled).toEqual(true);
|
||||
});
|
||||
} else {
|
||||
it('is enabled', () => {
|
||||
expect(loadToggle().props.disabled).toEqual(false);
|
||||
});
|
||||
}
|
||||
test('show email settings modal on click', () => {
|
||||
expect(loadToggle().props.onClick).toEqual(props.emailSettings.show);
|
||||
});
|
||||
});
|
||||
};
|
||||
const testFacebookShareButton = () => {
|
||||
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 = () => {
|
||||
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', () => {
|
||||
beforeEach(() => {
|
||||
mockHooks({
|
||||
facebook: { isEnabled: true },
|
||||
twitter: { isEnabled: true },
|
||||
isEmailEnabled: true,
|
||||
});
|
||||
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();
|
||||
testTwitterShareButton();
|
||||
});
|
||||
describe('only email enabled', () => {
|
||||
beforeEach(() => {
|
||||
mockHooks({ isEmailEnabled: true });
|
||||
render();
|
||||
});
|
||||
testEmailSettingsDropdown();
|
||||
it('does not render facebook or twitter controls', () => {
|
||||
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 });
|
||||
render();
|
||||
});
|
||||
testEmailSettingsDropdown(true);
|
||||
});
|
||||
});
|
||||
describe('only facebook enabled', () => {
|
||||
beforeEach(() => {
|
||||
mockHooks({ facebook: { isEnabled: true } });
|
||||
render();
|
||||
});
|
||||
testFacebookShareButton();
|
||||
it('does not render email or twitter controls', () => {
|
||||
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 } });
|
||||
render();
|
||||
});
|
||||
testTwitterShareButton();
|
||||
it('does not render email or facebook controls', () => {
|
||||
expect(el.instance.findByTestId(testIds.emailSettingsModalToggle).length).toEqual(0);
|
||||
expect(el.instance.findByType(ReactShare.FacebookShareButton).length).toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,10 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CourseCardMenu disable and stop rendering buttons snapshot when no dropdown items exist 1`] = `
|
||||
exports[`CourseCardMenu render show dropdown hide unenroll item and disable email snapshot 1`] = `
|
||||
<Fragment>
|
||||
<Dropdown>
|
||||
<Dropdown
|
||||
onToggle={[MockFunction hooks.handleToggleDropdown]}
|
||||
>
|
||||
<Dropdown.Toggle
|
||||
alt="Course actions dropdown"
|
||||
as="IconButton"
|
||||
@@ -12,38 +14,16 @@ exports[`CourseCardMenu disable and stop rendering buttons snapshot when no drop
|
||||
variant="primary"
|
||||
/>
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item
|
||||
data-testid="unenrollModalToggle"
|
||||
disabled={false}
|
||||
onClick={[MockFunction unenrollShow]}
|
||||
>
|
||||
Unenroll
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
data-testid="emailSettingsModalToggle"
|
||||
disabled={false}
|
||||
onClick={[MockFunction emailSettingShow]}
|
||||
>
|
||||
Email settings
|
||||
</Dropdown.Item>
|
||||
<FacebookShareButton
|
||||
className="pgn__dropdown-item dropdown-item"
|
||||
onClick={[MockFunction facebookShareClick]}
|
||||
resetButtonStyle={false}
|
||||
title="I'm taking test-course-name online with facebook-social-brand. Check it out!"
|
||||
url="facebook-share-url"
|
||||
>
|
||||
Share to Facebook
|
||||
</FacebookShareButton>
|
||||
<TwitterShareButton
|
||||
className="pgn__dropdown-item dropdown-item"
|
||||
onClick={[MockFunction twitterShareClick]}
|
||||
resetButtonStyle={false}
|
||||
title="I'm taking test-course-name online with twitter-social-brand. Check it out!"
|
||||
url="twitter-share-url"
|
||||
>
|
||||
Share to Twitter
|
||||
</TwitterShareButton>
|
||||
<SocialShareMenu
|
||||
cardId="test-card-id"
|
||||
emailSettings={
|
||||
Object {
|
||||
"hide": [MockFunction emailSettingHide],
|
||||
"isVisible": false,
|
||||
"show": [MockFunction emailSettingShow],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
<UnenrollConfirmModal
|
||||
@@ -51,17 +31,14 @@ exports[`CourseCardMenu disable and stop rendering buttons snapshot when no drop
|
||||
closeModal={[MockFunction unenrollHide]}
|
||||
show={false}
|
||||
/>
|
||||
<EmailSettingsModal
|
||||
cardId="test-card-id"
|
||||
closeModal={[MockFunction emailSettingHide]}
|
||||
show={false}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`CourseCardMenu enrolled, share enabled, email setting enable snapshot 1`] = `
|
||||
exports[`CourseCardMenu render show dropdown show unenroll and enable email snapshot 1`] = `
|
||||
<Fragment>
|
||||
<Dropdown>
|
||||
<Dropdown
|
||||
onToggle={[MockFunction hooks.handleToggleDropdown]}
|
||||
>
|
||||
<Dropdown.Toggle
|
||||
alt="Course actions dropdown"
|
||||
as="IconButton"
|
||||
@@ -78,90 +55,16 @@ exports[`CourseCardMenu enrolled, share enabled, email setting enable snapshot 1
|
||||
>
|
||||
Unenroll
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
data-testid="emailSettingsModalToggle"
|
||||
disabled={false}
|
||||
onClick={[MockFunction emailSettingShow]}
|
||||
>
|
||||
Email settings
|
||||
</Dropdown.Item>
|
||||
<FacebookShareButton
|
||||
className="pgn__dropdown-item dropdown-item"
|
||||
onClick={[MockFunction facebookShareClick]}
|
||||
resetButtonStyle={false}
|
||||
title="I'm taking test-course-name online with facebook-social-brand. Check it out!"
|
||||
url="facebook-share-url"
|
||||
>
|
||||
Share to Facebook
|
||||
</FacebookShareButton>
|
||||
<TwitterShareButton
|
||||
className="pgn__dropdown-item dropdown-item"
|
||||
onClick={[MockFunction twitterShareClick]}
|
||||
resetButtonStyle={false}
|
||||
title="I'm taking test-course-name online with twitter-social-brand. Check it out!"
|
||||
url="twitter-share-url"
|
||||
>
|
||||
Share to Twitter
|
||||
</TwitterShareButton>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
<UnenrollConfirmModal
|
||||
cardId="test-card-id"
|
||||
closeModal={[MockFunction unenrollHide]}
|
||||
show={false}
|
||||
/>
|
||||
<EmailSettingsModal
|
||||
cardId="test-card-id"
|
||||
closeModal={[MockFunction emailSettingHide]}
|
||||
show={false}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`CourseCardMenu masquerading snapshot 1`] = `
|
||||
<Fragment>
|
||||
<Dropdown>
|
||||
<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={true}
|
||||
onClick={[MockFunction unenrollShow]}
|
||||
>
|
||||
Unenroll
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
data-testid="emailSettingsModalToggle"
|
||||
disabled={true}
|
||||
onClick={[MockFunction emailSettingShow]}
|
||||
>
|
||||
Email settings
|
||||
</Dropdown.Item>
|
||||
<FacebookShareButton
|
||||
className="pgn__dropdown-item dropdown-item"
|
||||
onClick={[MockFunction facebookShareClick]}
|
||||
resetButtonStyle={false}
|
||||
title="I'm taking test-course-name online with facebook-social-brand. Check it out!"
|
||||
url="facebook-share-url"
|
||||
>
|
||||
Share to Facebook
|
||||
</FacebookShareButton>
|
||||
<TwitterShareButton
|
||||
className="pgn__dropdown-item dropdown-item"
|
||||
onClick={[MockFunction twitterShareClick]}
|
||||
resetButtonStyle={false}
|
||||
title="I'm taking test-course-name online with twitter-social-brand. Check it out!"
|
||||
url="twitter-share-url"
|
||||
>
|
||||
Share to Twitter
|
||||
</TwitterShareButton>
|
||||
<SocialShareMenu
|
||||
cardId="test-card-id"
|
||||
emailSettings={
|
||||
Object {
|
||||
"hide": [MockFunction emailSettingHide],
|
||||
"isVisible": false,
|
||||
"show": [MockFunction emailSettingShow],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
<UnenrollConfirmModal
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
import React from 'react';
|
||||
import { StrictDict } from 'utils';
|
||||
import { useKeyedState, StrictDict } from '@edx/react-unit-test-utils';
|
||||
|
||||
import track from 'tracking';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import * as module from './hooks';
|
||||
|
||||
export const state = StrictDict({
|
||||
isUnenrollConfirmVisible: (val) => React.useState(val), // eslint-disable-line
|
||||
isEmailSettingsVisible: (val) => React.useState(val), // eslint-disable-line
|
||||
export const stateKeys = StrictDict({
|
||||
isUnenrollConfirmVisible: 'isUnenrollConfirmVisible',
|
||||
isEmailSettingsVisible: 'isEmailSettingsVisible',
|
||||
});
|
||||
|
||||
export const useUnenrollData = () => {
|
||||
const [isVisible, setIsVisible] = module.state.isUnenrollConfirmVisible(false);
|
||||
const [isVisible, setIsVisible] = useKeyedState(stateKeys.isUnenrollConfirmVisible, false);
|
||||
return {
|
||||
show: () => setIsVisible(true),
|
||||
hide: () => setIsVisible(false),
|
||||
@@ -21,7 +18,7 @@ export const useUnenrollData = () => {
|
||||
};
|
||||
|
||||
export const useEmailSettings = () => {
|
||||
const [isVisible, setIsVisible] = module.state.isEmailSettingsVisible(false);
|
||||
const [isVisible, setIsVisible] = useKeyedState(stateKeys.isEmailSettingsVisible, false);
|
||||
return {
|
||||
show: () => setIsVisible(true),
|
||||
hide: () => setIsVisible(false),
|
||||
@@ -30,9 +27,30 @@ export const useEmailSettings = () => {
|
||||
};
|
||||
|
||||
export const useHandleToggleDropdown = (cardId) => {
|
||||
const eventName = track.course.courseOptionsDropdownClicked;
|
||||
const trackCourseEvent = reduxHooks.useTrackCourseEvent(eventName, cardId);
|
||||
const trackCourseEvent = reduxHooks.useTrackCourseEvent(
|
||||
track.course.courseOptionsDropdownClicked,
|
||||
cardId,
|
||||
);
|
||||
return (isOpen) => {
|
||||
if (isOpen) { trackCourseEvent(); }
|
||||
};
|
||||
};
|
||||
|
||||
export const useOptionVisibility = (cardId) => {
|
||||
const { isEnrolled, isEmailEnabled } = reduxHooks.useCardEnrollmentData(cardId);
|
||||
const { twitter, facebook } = reduxHooks.useCardSocialSettingsData(cardId);
|
||||
const { isEarned } = reduxHooks.useCardCertificateData(cardId);
|
||||
|
||||
const shouldShowUnenrollItem = isEnrolled && !isEarned;
|
||||
const shouldShowDropdown = (
|
||||
shouldShowUnenrollItem
|
||||
|| isEmailEnabled
|
||||
|| facebook.isEnabled
|
||||
|| twitter.isEnabled
|
||||
);
|
||||
|
||||
return {
|
||||
shouldShowUnenrollItem,
|
||||
shouldShowDropdown,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { MockUseState } from 'testUtils';
|
||||
import { mockUseKeyedState } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import track from 'tracking';
|
||||
|
||||
@@ -6,71 +7,77 @@ import * as hooks from './hooks';
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCertificateData: jest.fn(),
|
||||
useCardEnrollmentData: jest.fn(),
|
||||
useCardSocialSettingsData: jest.fn(),
|
||||
useTrackCourseEvent: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const trackCourseEvent = jest.fn();
|
||||
reduxHooks.useTrackCourseEvent.mockReturnValue(trackCourseEvent);
|
||||
const state = new MockUseState(hooks);
|
||||
|
||||
const cardId = 'test-card-id';
|
||||
let out;
|
||||
|
||||
describe('CourseCardMenu hooks', () => {
|
||||
describe('state values', () => {
|
||||
state.testGetter(state.keys.isUnenrollConfirmVisible);
|
||||
state.testGetter(state.keys.isEmailSettingsVisible);
|
||||
});
|
||||
const state = mockUseKeyedState(hooks.stateKeys);
|
||||
|
||||
describe('CourseCardMenu hooks', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
state.mock();
|
||||
});
|
||||
describe('useUnenrollData', () => {
|
||||
beforeEach(() => {
|
||||
state.mock();
|
||||
state.mockVals({ isUnenrollConfirmVisible: true });
|
||||
out = hooks.useUnenrollData();
|
||||
});
|
||||
afterEach(state.restore);
|
||||
|
||||
test('default state', () => {
|
||||
expect(out.isVisible).toEqual(state.stateVals.isUnenrollConfirmVisible);
|
||||
describe('behavior', () => {
|
||||
it('initializes isUnenrollConfirmVisible state to false', () => {
|
||||
state.expectInitializedWith(state.keys.isUnenrollConfirmVisible, false);
|
||||
});
|
||||
});
|
||||
|
||||
test('show', () => {
|
||||
out.show();
|
||||
state.expectSetStateCalledWith(state.keys.isUnenrollConfirmVisible, true);
|
||||
});
|
||||
|
||||
test('hide', () => {
|
||||
out.hide();
|
||||
state.expectSetStateCalledWith(state.keys.isUnenrollConfirmVisible, false);
|
||||
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);
|
||||
});
|
||||
test('hide sets state value to false', () => {
|
||||
out.hide();
|
||||
expect(state.setState.isUnenrollConfirmVisible).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('useEmailSettings', () => {
|
||||
beforeEach(() => {
|
||||
state.mock();
|
||||
state.mockVals({ isEmailSettingsVisible: true });
|
||||
out = hooks.useEmailSettings();
|
||||
});
|
||||
afterEach(state.restore);
|
||||
|
||||
test('default state', () => {
|
||||
expect(out.isVisible).toEqual(state.stateVals.isEmailSettingsVisible);
|
||||
describe('behavior', () => {
|
||||
it('initializes isEmailSettingsVisible state to false', () => {
|
||||
state.expectInitializedWith(state.keys.isEmailSettingsVisible, false);
|
||||
});
|
||||
});
|
||||
|
||||
test('show', () => {
|
||||
out.show();
|
||||
state.expectSetStateCalledWith(state.keys.isEmailSettingsVisible, true);
|
||||
});
|
||||
|
||||
test('hide', () => {
|
||||
out.hide();
|
||||
state.expectSetStateCalledWith(state.keys.isEmailSettingsVisible, false);
|
||||
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);
|
||||
});
|
||||
test('hide sets state value to false', () => {
|
||||
out.hide();
|
||||
expect(state.setState.isEmailSettingsVisible).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('useHandleToggleDropdown', () => {
|
||||
beforeEach(() => {
|
||||
out = hooks.useHandleToggleDropdown(cardId);
|
||||
});
|
||||
beforeEach(() => { out = hooks.useHandleToggleDropdown(cardId); });
|
||||
describe('behavior', () => {
|
||||
it('initializes course event tracker with event name and card ID', () => {
|
||||
expect(reduxHooks.useTrackCourseEvent).toHaveBeenCalledWith(
|
||||
@@ -88,4 +95,59 @@ describe('CourseCardMenu hooks', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('useOptionVisibility', () => {
|
||||
const mockReduxHooks = (returnVals = {}) => {
|
||||
reduxHooks.useCardSocialSettingsData.mockReturnValueOnce({
|
||||
facebook: { isEnabled: !!returnVals.facebook?.isEnabled },
|
||||
twitter: { isEnabled: !!returnVals.twitter?.isEnabled },
|
||||
});
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({
|
||||
isEnrolled: !!returnVals.isEnrolled,
|
||||
isEmailEnabled: !!returnVals.isEmailEnabled,
|
||||
});
|
||||
reduxHooks.useCardCertificateData.mockReturnValueOnce({
|
||||
isEarned: !!returnVals.isEarned,
|
||||
});
|
||||
};
|
||||
describe('shouldShowUnenrollItem', () => {
|
||||
it('returns true if enrolled and not earned', () => {
|
||||
mockReduxHooks({ isEnrolled: true });
|
||||
expect(hooks.useOptionVisibility(cardId).shouldShowUnenrollItem).toEqual(true);
|
||||
});
|
||||
it('returns false if not enrolled', () => {
|
||||
mockReduxHooks();
|
||||
expect(hooks.useOptionVisibility(cardId).shouldShowUnenrollItem).toEqual(false);
|
||||
});
|
||||
it('returns false if enrolled but also earned', () => {
|
||||
mockReduxHooks({ isEarned: true });
|
||||
expect(hooks.useOptionVisibility(cardId).shouldShowUnenrollItem).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('shouldShowDropdown', () => {
|
||||
it('returns false if not enrolled and both email and socials are disabled', () => {
|
||||
mockReduxHooks();
|
||||
expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(false);
|
||||
});
|
||||
it('returns false if enrolled but already earned, and both email and socials are disabled', () => {
|
||||
mockReduxHooks({ isEnrolled: true, isEarned: true });
|
||||
expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(false);
|
||||
});
|
||||
it('returns true if either social is enabled', () => {
|
||||
mockReduxHooks({ facebook: { isEnabled: true } });
|
||||
expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(true);
|
||||
mockReduxHooks({ twitter: { isEnabled: true } });
|
||||
expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(true);
|
||||
});
|
||||
it('returns true if email is enabled', () => {
|
||||
mockReduxHooks({ isEmailEnabled: true });
|
||||
expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(true);
|
||||
});
|
||||
it('returns true if enrolled and not earned', () => {
|
||||
mockReduxHooks({ isEnrolled: true });
|
||||
expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,50 +1,41 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as ReactShare from 'react-share';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Dropdown, Icon, IconButton } from '@edx/paragon';
|
||||
import { MoreVert } from '@edx/paragon/icons';
|
||||
import { StrictDict } from '@edx/react-unit-test-utils';
|
||||
|
||||
import track from 'tracking';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import EmailSettingsModal from 'containers/EmailSettingsModal';
|
||||
import CertificatePreviewModal from 'containers/CertificatePreviewModal';
|
||||
import UnenrollConfirmModal from 'containers/UnenrollConfirmModal';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import SocialShareMenu from './SocialShareMenu';
|
||||
import {
|
||||
useEmailSettings,
|
||||
useUnenrollData,
|
||||
useHandleToggleDropdown,
|
||||
useOptionVisibility,
|
||||
} from './hooks';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
export const testIds = StrictDict({
|
||||
unenrollModalToggle: 'unenrollModalToggle',
|
||||
});
|
||||
|
||||
export const CourseCardMenu = ({ cardId }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const { courseName } = reduxHooks.useCardCourseData(cardId);
|
||||
const { isEnrolled, isEmailEnabled } = reduxHooks.useCardEnrollmentData(cardId);
|
||||
const { twitter, facebook } = reduxHooks.useCardSocialSettingsData(cardId);
|
||||
const { isMasquerading } = reduxHooks.useMasqueradeData();
|
||||
const { isEarned } = reduxHooks.useCardCertificateData(cardId);
|
||||
const handleTwitterShare = reduxHooks.useTrackCourseEvent(
|
||||
track.socialShare,
|
||||
cardId,
|
||||
'twitter',
|
||||
);
|
||||
const handleFacebookShare = reduxHooks.useTrackCourseEvent(
|
||||
track.socialShare,
|
||||
cardId,
|
||||
'facebook',
|
||||
);
|
||||
|
||||
const emailSettingsModal = useEmailSettings();
|
||||
const emailSettings = useEmailSettings();
|
||||
const unenrollModal = useUnenrollData();
|
||||
const handleToggleDropdown = useHandleToggleDropdown(cardId);
|
||||
const { shouldShowUnenrollItem, shouldShowDropdown } = useOptionVisibility(cardId);
|
||||
const { isMasquerading } = reduxHooks.useMasqueradeData();
|
||||
const { isEmailEnabled } = reduxHooks.useCardEnrollmentData(cardId);
|
||||
const showCertificatePreviewModal = reduxHooks.useShowCertificatePreviewModal(cardId);
|
||||
|
||||
const showUnenrollItem = isEnrolled && !isEarned;
|
||||
const showDropdown = showUnenrollItem || isEmailEnabled || facebook.isEnabled || twitter.isEnabled;
|
||||
|
||||
if (!showDropdown) {
|
||||
if (!shouldShowDropdown) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -60,52 +51,16 @@ export const CourseCardMenu = ({ cardId }) => {
|
||||
alt={formatMessage(messages.dropdownAlt)}
|
||||
/>
|
||||
<Dropdown.Menu>
|
||||
{showUnenrollItem && (
|
||||
{shouldShowUnenrollItem && (
|
||||
<Dropdown.Item
|
||||
disabled={isMasquerading}
|
||||
onClick={unenrollModal.show}
|
||||
data-testid="unenrollModalToggle"
|
||||
data-testid={testIds.unenrollModalToggle}
|
||||
>
|
||||
{formatMessage(messages.unenroll)}
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
{isEmailEnabled && (
|
||||
<Dropdown.Item
|
||||
disabled={isMasquerading}
|
||||
onClick={emailSettingsModal.show}
|
||||
data-testid="emailSettingsModalToggle"
|
||||
>
|
||||
{formatMessage(messages.emailSettings)}
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
{facebook.isEnabled && (
|
||||
<ReactShare.FacebookShareButton
|
||||
url={facebook.shareUrl}
|
||||
onClick={handleFacebookShare}
|
||||
title={formatMessage(messages.shareQuote, {
|
||||
courseName,
|
||||
socialBrand: facebook.socialBrand,
|
||||
})}
|
||||
resetButtonStyle={false}
|
||||
className="pgn__dropdown-item dropdown-item"
|
||||
>
|
||||
{formatMessage(messages.shareToFacebook)}
|
||||
</ReactShare.FacebookShareButton>
|
||||
)}
|
||||
{twitter.isEnabled && (
|
||||
<ReactShare.TwitterShareButton
|
||||
url={twitter.shareUrl}
|
||||
onClick={handleTwitterShare}
|
||||
title={formatMessage(messages.shareQuote, {
|
||||
courseName,
|
||||
socialBrand: twitter.socialBrand,
|
||||
})}
|
||||
resetButtonStyle={false}
|
||||
className="pgn__dropdown-item dropdown-item"
|
||||
>
|
||||
{formatMessage(messages.shareToTwitter)}
|
||||
</ReactShare.TwitterShareButton>
|
||||
)}
|
||||
<SocialShareMenu cardId={cardId} emailSettings={emailSettings} />
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
<UnenrollConfirmModal
|
||||
@@ -115,8 +70,13 @@ export const CourseCardMenu = ({ cardId }) => {
|
||||
/>
|
||||
{isEmailEnabled && (
|
||||
<EmailSettingsModal
|
||||
show={emailSettingsModal.isVisible}
|
||||
closeModal={emailSettingsModal.hide}
|
||||
show={emailSettings.isVisible}
|
||||
closeModal={emailSettings.hide}
|
||||
cardId={cardId}
|
||||
/>
|
||||
)}
|
||||
{showCertificatePreviewModal && (
|
||||
<CertificatePreviewModal
|
||||
cardId={cardId}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,216 +1,213 @@
|
||||
import { shallow } from 'enzyme';
|
||||
import { when } from 'jest-when';
|
||||
|
||||
import { Dropdown } from '@edx/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 { useEmailSettings, useUnenrollData } from './hooks';
|
||||
import CourseCardMenu from '.';
|
||||
import SocialShareMenu from './SocialShareMenu';
|
||||
import * as hooks from './hooks';
|
||||
import CourseCardMenu, { testIds } from '.';
|
||||
|
||||
jest.mock('react-share', () => ({
|
||||
FacebookShareButton: () => 'FacebookShareButton',
|
||||
TwitterShareButton: () => 'TwitterShareButton',
|
||||
jest.mock('@edx/frontend-platform/i18n', () => ({
|
||||
useIntl: jest.fn().mockReturnValue({
|
||||
formatMessage: jest.requireActual('@edx/react-unit-test-utils').formatMessage,
|
||||
}),
|
||||
}));
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCourseData: jest.fn(),
|
||||
useCardEnrollmentData: jest.fn(),
|
||||
useCardSocialSettingsData: jest.fn(),
|
||||
useMasqueradeData: jest.fn(),
|
||||
useCardCertificateData: jest.fn(),
|
||||
useTrackCourseEvent: (_, __, site) => jest.fn().mockName(`${site}ShareClick`),
|
||||
},
|
||||
reduxHooks: { useMasqueradeData: jest.fn(), useCardEnrollmentData: jest.fn() },
|
||||
}));
|
||||
jest.mock('./SocialShareMenu', () => 'SocialShareMenu');
|
||||
jest.mock('./hooks', () => ({
|
||||
useEmailSettings: jest.fn(),
|
||||
useUnenrollData: jest.fn(),
|
||||
useHandleToggleDropdown: jest.fn(),
|
||||
useOptionVisibility: jest.fn(),
|
||||
}));
|
||||
|
||||
const props = {
|
||||
cardId: 'test-card-id',
|
||||
};
|
||||
const defaultEmailSettingsModal = {
|
||||
|
||||
const emailSettings = {
|
||||
isVisible: false,
|
||||
show: jest.fn().mockName('emailSettingShow'),
|
||||
hide: jest.fn().mockName('emailSettingHide'),
|
||||
};
|
||||
const defaultUnenrollModal = {
|
||||
|
||||
const unenrollData = {
|
||||
isVisible: false,
|
||||
show: jest.fn().mockName('unenrollShow'),
|
||||
hide: jest.fn().mockName('unenrollHide'),
|
||||
};
|
||||
const defaultSocialShare = {
|
||||
facebook: {
|
||||
isEnabled: true,
|
||||
shareUrl: 'facebook-share-url',
|
||||
socialBrand: 'facebook-social-brand',
|
||||
},
|
||||
twitter: {
|
||||
isEnabled: true,
|
||||
shareUrl: 'twitter-share-url',
|
||||
socialBrand: 'twitter-social-brand',
|
||||
},
|
||||
};
|
||||
const courseName = 'test-course-name';
|
||||
let wrapper;
|
||||
|
||||
let el;
|
||||
|
||||
const mockHook = (fn, returnValue, options = {}) => {
|
||||
if (options.isCardHook) {
|
||||
when(fn).calledWith(props.cardId).mockReturnValueOnce(returnValue);
|
||||
} else {
|
||||
when(fn).calledWith().mockReturnValueOnce(returnValue);
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleDropdown = jest.fn().mockName('hooks.handleToggleDropdown');
|
||||
|
||||
const mockHooks = (returnVals = {}) => {
|
||||
mockHook(
|
||||
hooks.useEmailSettings,
|
||||
returnVals.emailSettings ? returnVals.emailSettings : emailSettings,
|
||||
);
|
||||
mockHook(
|
||||
hooks.useUnenrollData,
|
||||
returnVals.unenrollData ? returnVals.unenrollData : unenrollData,
|
||||
);
|
||||
mockHook(hooks.useHandleToggleDropdown, handleToggleDropdown, { isCardHook: true });
|
||||
mockHook(
|
||||
hooks.useOptionVisibility,
|
||||
{
|
||||
shouldShowUnenrollItem: !!returnVals.shouldShowUnenrollItem,
|
||||
shouldShowDropdown: !!returnVals.shouldShowDropdown,
|
||||
},
|
||||
{ isCardHook: true },
|
||||
);
|
||||
mockHook(reduxHooks.useMasqueradeData, { isMasquerading: !!returnVals.isMasquerading });
|
||||
mockHook(
|
||||
reduxHooks.useCardEnrollmentData,
|
||||
{ isEmailEnabled: !!returnVals.isEmailEnabled },
|
||||
{ isCardHook: true },
|
||||
);
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
el = shallow(<CourseCardMenu {...props} />);
|
||||
};
|
||||
|
||||
describe('CourseCardMenu', () => {
|
||||
const mockCourseCardMenu = ({
|
||||
isEnrolled,
|
||||
isEmailEnabled,
|
||||
isMasquerading,
|
||||
facebook,
|
||||
twitter,
|
||||
isEarned,
|
||||
}) => {
|
||||
useEmailSettings.mockReturnValueOnce(defaultEmailSettingsModal);
|
||||
useUnenrollData.mockReturnValueOnce(defaultUnenrollModal);
|
||||
reduxHooks.useCardCourseData.mockReturnValueOnce({ courseName });
|
||||
reduxHooks.useCardSocialSettingsData.mockReturnValueOnce({
|
||||
facebook: {
|
||||
...defaultSocialShare.facebook,
|
||||
...facebook,
|
||||
},
|
||||
twitter: {
|
||||
...defaultSocialShare.twitter,
|
||||
...twitter,
|
||||
},
|
||||
});
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({
|
||||
isEnrolled,
|
||||
isEmailEnabled,
|
||||
});
|
||||
reduxHooks.useMasqueradeData.mockReturnValueOnce({ isMasquerading });
|
||||
reduxHooks.useCardCertificateData.mockReturnValueOnce({ isEarned });
|
||||
return shallow(<CourseCardMenu {...props} />);
|
||||
};
|
||||
describe('enrolled, share enabled, email setting enable', () => {
|
||||
describe('behavior', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = mockCourseCardMenu({
|
||||
isEnrolled: true,
|
||||
isEmailEnabled: true,
|
||||
isMasquerading: false,
|
||||
isEarned: false,
|
||||
});
|
||||
mockHooks();
|
||||
render();
|
||||
});
|
||||
test('snapshot', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
it('initializes intl hook', () => {
|
||||
expect(useIntl).toHaveBeenCalledWith();
|
||||
});
|
||||
it('renders share buttons', () => {
|
||||
el = wrapper.find('FacebookShareButton');
|
||||
expect(el.length).toEqual(1);
|
||||
expect(el.prop('url')).toEqual('facebook-share-url');
|
||||
el = wrapper.find('TwitterShareButton');
|
||||
expect(el.length).toEqual(1);
|
||||
expect(el.prop('url')).toEqual('twitter-share-url');
|
||||
it('initializes local hooks', () => {
|
||||
when(hooks.useEmailSettings).expectCalledWith();
|
||||
when(hooks.useUnenrollData).expectCalledWith();
|
||||
when(hooks.useHandleToggleDropdown).expectCalledWith(props.cardId);
|
||||
when(hooks.useOptionVisibility).expectCalledWith(props.cardId);
|
||||
});
|
||||
it('renders enabled unenroll modal toggle', () => {
|
||||
el = wrapper.find({ 'data-testid': 'unenrollModalToggle' });
|
||||
expect(el.props().disabled).toEqual(false);
|
||||
});
|
||||
it('renders enabled email settings modal toggle', () => {
|
||||
el = wrapper.find({ 'data-testid': 'emailSettingsModalToggle' });
|
||||
expect(el.props().disabled).toEqual(false);
|
||||
});
|
||||
it('renders enabled email settings modal toggle', () => {
|
||||
el = wrapper.find({ 'data-testid': 'emailSettingsModalToggle' });
|
||||
expect(el.props().disabled).toEqual(false);
|
||||
it('initializes redux hook data ', () => {
|
||||
when(reduxHooks.useMasqueradeData).expectCalledWith();
|
||||
when(reduxHooks.useCardEnrollmentData).expectCalledWith(props.cardId);
|
||||
});
|
||||
});
|
||||
describe('disable and stop rendering buttons', () => {
|
||||
it('does not render unenroll dropdown item when certificate is already earned', () => {
|
||||
wrapper = mockCourseCardMenu({
|
||||
isEnrolled: true,
|
||||
isEmailEnabled: true,
|
||||
isMasquerading: false,
|
||||
isEarned: true,
|
||||
describe('render', () => {
|
||||
it('renders null if showDropdown is false', () => {
|
||||
mockHooks();
|
||||
render();
|
||||
expect(el.isEmptyRender()).toEqual(true);
|
||||
});
|
||||
const testHandleToggle = () => {
|
||||
it('displays Dropdown with onToggle=handleToggleDropdown', () => {
|
||||
expect(el.instance.findByType(Dropdown)[0].props.onToggle).toEqual(handleToggleDropdown);
|
||||
});
|
||||
el = wrapper.find({ 'data-testid': 'unenrollModalToggle' });
|
||||
expect(el.length).toEqual(0);
|
||||
});
|
||||
it('does not render unenroll dropdown item when course is not enrolled', () => {
|
||||
wrapper = mockCourseCardMenu({
|
||||
isEnrolled: false,
|
||||
isEmailEnabled: true,
|
||||
isMasquerading: false,
|
||||
isEarned: false,
|
||||
};
|
||||
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);
|
||||
});
|
||||
el = wrapper.find({ 'data-testid': 'unenrollModalToggle' });
|
||||
expect(el.length).toEqual(0);
|
||||
});
|
||||
it('does not render email settings modal toggle when email is not enabled', () => {
|
||||
wrapper = mockCourseCardMenu({
|
||||
isEnrolled: true,
|
||||
isEmailEnabled: false,
|
||||
isMasquerading: false,
|
||||
isEarned: false,
|
||||
};
|
||||
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);
|
||||
});
|
||||
el = wrapper.find({ 'data-testid': 'emailSettingsModalToggle' });
|
||||
expect(el.length).toEqual(0);
|
||||
});
|
||||
it('does not render facebook share button when facebook is not enabled', () => {
|
||||
wrapper = mockCourseCardMenu({
|
||||
isEnrolled: true,
|
||||
isEmailEnabled: true,
|
||||
facebook: {
|
||||
...defaultSocialShare.facebook,
|
||||
isEnabled: false,
|
||||
},
|
||||
isMasquerading: false,
|
||||
isEarned: false,
|
||||
};
|
||||
describe('show dropdown', () => {
|
||||
describe('hide unenroll item and disable email', () => {
|
||||
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();
|
||||
});
|
||||
el = wrapper.find('FacebookShareButton');
|
||||
expect(el.length).toEqual(0);
|
||||
});
|
||||
it('does not render twitter share button when twitter is not enabled', () => {
|
||||
wrapper = mockCourseCardMenu({
|
||||
isEnrolled: true,
|
||||
isEmailEnabled: true,
|
||||
twitter: {
|
||||
...defaultSocialShare.twitter,
|
||||
isEnabled: false,
|
||||
},
|
||||
isMasquerading: false,
|
||||
isEarned: false,
|
||||
describe('show unenroll and enable email', () => {
|
||||
const hookProps = {
|
||||
shouldShowDropdown: true,
|
||||
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', () => {
|
||||
beforeEach(() => {
|
||||
mockHooks(hookProps);
|
||||
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', () => {
|
||||
beforeEach(() => {
|
||||
mockHooks({ ...hookProps, isMasquerading: true });
|
||||
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);
|
||||
});
|
||||
});
|
||||
el = wrapper.find('TwitterShareButton');
|
||||
expect(el.length).toEqual(0);
|
||||
});
|
||||
it('snapshot when no dropdown items exist', () => {
|
||||
wrapper = mockCourseCardMenu({
|
||||
isEnrolled: true,
|
||||
isEmailEnabled: true,
|
||||
isMasquerading: false,
|
||||
isEarned: false,
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper).toEqual({});
|
||||
});
|
||||
});
|
||||
describe('masquerading', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = mockCourseCardMenu({
|
||||
isEnrolled: true,
|
||||
isEmailEnabled: true,
|
||||
isMasquerading: true,
|
||||
isEarned: false,
|
||||
});
|
||||
});
|
||||
test('snapshot', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it('renders share buttons', () => {
|
||||
expect(wrapper.find('FacebookShareButton').length).toEqual(1);
|
||||
el = wrapper.find('TwitterShareButton');
|
||||
expect(el.length).toEqual(1);
|
||||
expect(el.prop('url')).toEqual('twitter-share-url');
|
||||
});
|
||||
it('renders disabled unenroll modal toggle', () => {
|
||||
el = wrapper.find({ 'data-testid': 'unenrollModalToggle' });
|
||||
expect(el.props().disabled).toEqual(true);
|
||||
});
|
||||
it('renders disabled email settings modal toggle', () => {
|
||||
el = wrapper.find({ 'data-testid': 'emailSettingsModalToggle' });
|
||||
expect(el.props().disabled).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -63,143 +63,124 @@ describe('useActionDisabledState', () => {
|
||||
});
|
||||
};
|
||||
|
||||
const runHook = () => hooks.useActionDisabledState(cardId);
|
||||
describe('disableBeginCourse', () => {
|
||||
const testDisabled = (data, expected) => {
|
||||
mockHooksData(data);
|
||||
expect(runHook().disableBeginCourse).toBe(expected);
|
||||
};
|
||||
it('disable when homeUrl is invalid', () => {
|
||||
mockHooksData({ homeUrl: null });
|
||||
const { disableBeginCourse } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableBeginCourse).toBe(true);
|
||||
testDisabled({ homeUrl: null }, true);
|
||||
});
|
||||
it('disable when isMasquerading is true', () => {
|
||||
mockHooksData({ isMasquerading: true });
|
||||
const { disableBeginCourse } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableBeginCourse).toBe(true);
|
||||
testDisabled({ isMasquerading: true }, true);
|
||||
});
|
||||
it('disable when hasAccess is false', () => {
|
||||
mockHooksData({ hasAccess: false });
|
||||
const { disableBeginCourse } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableBeginCourse).toBe(true);
|
||||
testDisabled({ hasAccess: false }, true);
|
||||
});
|
||||
it('disable when isAudit is true and isAuditAccessExpired is true', () => {
|
||||
mockHooksData({ isAudit: true, isAuditAccessExpired: true });
|
||||
const { disableBeginCourse } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableBeginCourse).toBe(true);
|
||||
testDisabled({ isAudit: true, isAuditAccessExpired: true }, true);
|
||||
});
|
||||
it('enable when all conditions are met', () => {
|
||||
mockHooksData({ hasAccess: true });
|
||||
const { disableBeginCourse } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableBeginCourse).toBe(false);
|
||||
testDisabled({ hasAccess: true }, false);
|
||||
});
|
||||
});
|
||||
describe('disableResumeCourse', () => {
|
||||
const testDisabled = (data, expected) => {
|
||||
mockHooksData(data);
|
||||
expect(runHook().disableResumeCourse).toBe(expected);
|
||||
};
|
||||
it('disable when resumeUrl is invalid', () => {
|
||||
mockHooksData({ resumeUrl: null });
|
||||
const { disableResumeCourse } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableResumeCourse).toBe(true);
|
||||
testDisabled({ resumeUrl: null }, true);
|
||||
});
|
||||
it('disable when isMasquerading is true', () => {
|
||||
mockHooksData({ isMasquerading: true });
|
||||
const { disableResumeCourse } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableResumeCourse).toBe(true);
|
||||
testDisabled({ isMasquerading: true }, true);
|
||||
});
|
||||
it('disable when hasAccess is false', () => {
|
||||
mockHooksData({ hasAccess: false });
|
||||
const { disableResumeCourse } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableResumeCourse).toBe(true);
|
||||
testDisabled({ hasAccess: false }, true);
|
||||
});
|
||||
it('disable when isAudit is true and isAuditAccessExpired is true', () => {
|
||||
mockHooksData({ isAudit: true, isAuditAccessExpired: true });
|
||||
const { disableResumeCourse } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableResumeCourse).toBe(true);
|
||||
testDisabled({ isAudit: true, isAuditAccessExpired: true }, true);
|
||||
});
|
||||
it('enable when all conditions are met', () => {
|
||||
mockHooksData({ hasAccess: true });
|
||||
const { disableResumeCourse } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableResumeCourse).toBe(false);
|
||||
testDisabled({ hasAccess: true }, false);
|
||||
});
|
||||
});
|
||||
describe('disableViewCourse', () => {
|
||||
const testDisabled = (data, expected) => {
|
||||
mockHooksData(data);
|
||||
expect(runHook().disableViewCourse).toBe(expected);
|
||||
};
|
||||
it('disable when hasAccess is false', () => {
|
||||
mockHooksData({ hasAccess: false });
|
||||
const { disableViewCourse } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableViewCourse).toBe(true);
|
||||
testDisabled({ hasAccess: false }, true);
|
||||
});
|
||||
it('disable when isAudit is true and isAuditAccessExpired is true', () => {
|
||||
mockHooksData({ isAudit: true, isAuditAccessExpired: true });
|
||||
const { disableViewCourse } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableViewCourse).toBe(true);
|
||||
testDisabled({ isAudit: true, isAuditAccessExpired: true }, true);
|
||||
});
|
||||
it('enable when all conditions are met', () => {
|
||||
mockHooksData({ hasAccess: true });
|
||||
const { disableViewCourse } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableViewCourse).toBe(false);
|
||||
testDisabled({ hasAccess: true }, false);
|
||||
});
|
||||
});
|
||||
describe('disableUpgradeCourse', () => {
|
||||
const testDisabled = (data, expected) => {
|
||||
mockHooksData(data);
|
||||
expect(runHook().disableUpgradeCourse).toBe(expected);
|
||||
};
|
||||
it('disable when upgradeUrl is invalid', () => {
|
||||
mockHooksData({ upgradeUrl: null });
|
||||
const { disableUpgradeCourse } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableUpgradeCourse).toBe(true);
|
||||
testDisabled({ upgradeUrl: null }, true);
|
||||
});
|
||||
it('disable when isMasquerading is true and canUpgrade is false', () => {
|
||||
mockHooksData({ isMasquerading: true, canUpgrade: false });
|
||||
const { disableUpgradeCourse } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableUpgradeCourse).toBe(true);
|
||||
testDisabled({ isMasquerading: true, canUpgrade: false }, true);
|
||||
});
|
||||
it('enable when all conditions are met', () => {
|
||||
mockHooksData({ canUpgrade: true });
|
||||
const { disableUpgradeCourse } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableUpgradeCourse).toBe(false);
|
||||
testDisabled({ canUpgrade: true }, false);
|
||||
});
|
||||
});
|
||||
describe('disableSelectSession', () => {
|
||||
const testDisabled = (data, expected) => {
|
||||
mockHooksData(data);
|
||||
expect(runHook().disableSelectSession).toBe(expected);
|
||||
};
|
||||
it('disable when isEntitlement is false', () => {
|
||||
mockHooksData({ isEntitlement: false });
|
||||
const { disableSelectSession } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableSelectSession).toBe(true);
|
||||
testDisabled({ isEntitlement: false }, true);
|
||||
});
|
||||
it('disable when isMasquerading is true', () => {
|
||||
mockHooksData({ isMasquerading: true });
|
||||
const { disableSelectSession } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableSelectSession).toBe(true);
|
||||
testDisabled({ isMasquerading: true }, true);
|
||||
});
|
||||
it('disable when hasAccess is false', () => {
|
||||
mockHooksData({ hasAccess: false });
|
||||
const { disableSelectSession } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableSelectSession).toBe(true);
|
||||
testDisabled({ hasAccess: false }, true);
|
||||
});
|
||||
it('disable when canChange is false', () => {
|
||||
mockHooksData({ canChange: false });
|
||||
const { disableSelectSession } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableSelectSession).toBe(true);
|
||||
testDisabled({ canChange: false }, true);
|
||||
});
|
||||
it('disable when hasSessions is false', () => {
|
||||
mockHooksData({ hasSessions: false });
|
||||
const { disableSelectSession } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableSelectSession).toBe(true);
|
||||
testDisabled({ hasSessions: false }, true);
|
||||
});
|
||||
it('enable when all conditions are met', () => {
|
||||
mockHooksData({
|
||||
isEntitlement: true, hasAccess: true, canChange: true, hasSessions: true,
|
||||
});
|
||||
const { disableSelectSession } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableSelectSession).toBe(false);
|
||||
testDisabled(
|
||||
{
|
||||
isEntitlement: true,
|
||||
hasAccess: true,
|
||||
canChange: true,
|
||||
hasSessions: true,
|
||||
},
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('disableCourseTitle', () => {
|
||||
const testDisabled = (data, expected) => {
|
||||
mockHooksData(data);
|
||||
expect(runHook().disableCourseTitle).toBe(expected);
|
||||
};
|
||||
it('disable when isEntitlement is true and isFulfilled is false', () => {
|
||||
mockHooksData({ isEntitlement: true, isFulfilled: false });
|
||||
const { disableCourseTitle } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableCourseTitle).toBe(true);
|
||||
testDisabled({ isEntitlement: true, isFulfilled: false }, true);
|
||||
});
|
||||
it('disable when disableViewCourse is true', () => {
|
||||
mockHooksData({ hasAccess: false });
|
||||
const { disableCourseTitle } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableCourseTitle).toBe(true);
|
||||
testDisabled({ hasAccess: false }, true);
|
||||
});
|
||||
it('enable when all conditions are met', () => {
|
||||
mockHooksData({ isEntitlement: true, isFulfilled: true, hasAccess: true });
|
||||
const { disableCourseTitle } = hooks.useActionDisabledState(cardId);
|
||||
expect(disableCourseTitle).toBe(false);
|
||||
testDisabled({ isEntitlement: true, isFulfilled: true, hasAccess: true }, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ exports[`NoCoursesView snapshot 1`] = `
|
||||
</p>
|
||||
<Button
|
||||
as="a"
|
||||
href="course-search-url"
|
||||
href="http://localhost:18000/course-search-url"
|
||||
iconBefore={[MockFunction icons.Search]}
|
||||
variant="brand"
|
||||
>
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Button, Image } from '@edx/paragon';
|
||||
import { Search } from '@edx/paragon/icons';
|
||||
import { baseAppUrl } from 'data/services/lms/urls';
|
||||
|
||||
import emptyCourseSVG from 'assets/empty-course.svg';
|
||||
import { reduxHooks } from 'hooks';
|
||||
@@ -27,7 +28,7 @@ export const NoCoursesView = () => {
|
||||
<Button
|
||||
variant="brand"
|
||||
as="a"
|
||||
href={courseSearchUrl}
|
||||
href={baseAppUrl(courseSearchUrl)}
|
||||
iconBefore={Search}
|
||||
>
|
||||
{formatMessage(messages.exploreCoursesButton)}
|
||||
|
||||
@@ -6,7 +6,7 @@ import EmptyCourse from '.';
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
usePlatformSettingsData: jest.fn(() => ({
|
||||
courseSearchUrl: 'course-search-url',
|
||||
courseSearchUrl: '/course-search-url',
|
||||
})),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -3,12 +3,19 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import { Container, Col, Row } from '@edx/paragon';
|
||||
|
||||
import WidgetFooter from 'containers/WidgetContainers/WidgetFooter';
|
||||
import hooks from './hooks';
|
||||
|
||||
export const columnConfig = {
|
||||
courseList: {
|
||||
lg: { span: 12, offset: 0 },
|
||||
xl: { span: 8, offset: 0 },
|
||||
withSidebar: {
|
||||
lg: { span: 12, offset: 0 },
|
||||
xl: { span: 8, offset: 0 },
|
||||
},
|
||||
noSidebar: {
|
||||
lg: { span: 12, offset: 0 },
|
||||
xl: { span: 12, offset: 0 },
|
||||
},
|
||||
},
|
||||
sidebar: {
|
||||
lg: { span: 12, offset: 0 },
|
||||
@@ -16,18 +23,31 @@ export const columnConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
export const DashboardLayout = ({ children, sidebar }) => {
|
||||
const isCollapsed = hooks.useIsDashboardCollapsed();
|
||||
export const DashboardLayout = ({ children, sidebar: Sidebar }) => {
|
||||
const {
|
||||
isCollapsed,
|
||||
sidebarShowing,
|
||||
setSidebarShowing,
|
||||
} = hooks.useDashboardLayoutData();
|
||||
|
||||
const courseListColumnProps = sidebarShowing
|
||||
? columnConfig.courseList.withSidebar
|
||||
: columnConfig.courseList.noSidebar;
|
||||
|
||||
return (
|
||||
<Container fluid size="xl">
|
||||
<Row>
|
||||
<Col {...columnConfig.courseList} className="course-list-column">
|
||||
<Col {...courseListColumnProps} className="course-list-column">
|
||||
{children}
|
||||
</Col>
|
||||
<Col {...columnConfig.sidebar} className="sidebar-column">
|
||||
{!isCollapsed && (<h2 className="course-list-title"> </h2>)}
|
||||
{sidebar}
|
||||
<Sidebar setSidebarShowing={setSidebarShowing} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col>
|
||||
<WidgetFooter />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
@@ -35,7 +55,7 @@ export const DashboardLayout = ({ children, sidebar }) => {
|
||||
};
|
||||
DashboardLayout.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
sidebar: PropTypes.node.isRequired,
|
||||
sidebar: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default DashboardLayout;
|
||||
|
||||
@@ -1,60 +1,125 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Col, Row } from '@edx/paragon';
|
||||
|
||||
import WidgetFooter from 'containers/WidgetContainers/WidgetFooter';
|
||||
import hooks from './hooks';
|
||||
import DashboardLayout, { columnConfig } from './DashboardLayout';
|
||||
|
||||
jest.mock('./hooks', () => ({
|
||||
useIsDashboardCollapsed: jest.fn(() => true),
|
||||
useDashboardLayoutData: jest.fn(),
|
||||
}));
|
||||
|
||||
const hookProps = {
|
||||
isCollapsed: true,
|
||||
sidebarShowing: false,
|
||||
setSidebarShowing: jest.fn().mockName('hooks.setSidebarShowing'),
|
||||
};
|
||||
hooks.useDashboardLayoutData.mockReturnValue(hookProps);
|
||||
|
||||
const props = {
|
||||
sidebar: jest.fn(() => 'test-sidebar-content'),
|
||||
};
|
||||
|
||||
const children = 'test-children';
|
||||
|
||||
let el;
|
||||
describe('DashboardLayout', () => {
|
||||
const children = 'test-children';
|
||||
const props = {
|
||||
sidebar: 'test-sidebar-content',
|
||||
};
|
||||
const render = () => shallow(<DashboardLayout sidebar={props.sidebar}>{children}</DashboardLayout>);
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
el = shallow(<DashboardLayout {...props}>{children}</DashboardLayout>);
|
||||
});
|
||||
|
||||
const testColumns = () => {
|
||||
it('loads courseList and sidebar column layout', () => {
|
||||
const columns = render().find(Row).find(Col);
|
||||
Object.keys(columnConfig.courseList).forEach(size => {
|
||||
expect(columns.at(0).props()[size]).toEqual(columnConfig.courseList[size]);
|
||||
});
|
||||
const columns = el.find(Row).find(Col);
|
||||
Object.keys(columnConfig.sidebar).forEach(size => {
|
||||
expect(columns.at(1).props()[size]).toEqual(columnConfig.sidebar[size]);
|
||||
});
|
||||
});
|
||||
it('displays children in first column', () => {
|
||||
const columns = render().find(Row).find(Col);
|
||||
const columns = el.find(Row).find(Col);
|
||||
expect(columns.at(0).contains(children)).toEqual(true);
|
||||
});
|
||||
it('displays sidebar prop in second column', () => {
|
||||
const columns = render().find(Row).find(Col);
|
||||
expect(columns.at(1).contains(props.sidebar)).toEqual(true);
|
||||
const columns = el.find(Row).find(Col);
|
||||
expect(columns.at(1).find(props.sidebar)).toHaveLength(1);
|
||||
});
|
||||
it('displays a footer in the second row', () => {
|
||||
const columns = el.find(Row).at(1).find(Col);
|
||||
expect(columns.at(0).containsMatchingElement(<WidgetFooter />)).toBeTruthy();
|
||||
});
|
||||
};
|
||||
const testSidebarLayout = () => {
|
||||
it('displays widthSidebar width for course list column', () => {
|
||||
const columns = el.find(Row).find(Col);
|
||||
Object.keys(columnConfig.courseList.withSidebar).forEach(size => {
|
||||
expect(columns.at(0).props()[size]).toEqual(columnConfig.courseList.withSidebar[size]);
|
||||
});
|
||||
});
|
||||
};
|
||||
const testNoSidebarLayout = () => {
|
||||
it('displays noSidebar width for course list column', () => {
|
||||
const columns = el.find(Row).find(Col);
|
||||
Object.keys(columnConfig.courseList.noSidebar).forEach(size => {
|
||||
expect(columns.at(0).props()[size]).toEqual(columnConfig.courseList.noSidebar[size]);
|
||||
});
|
||||
});
|
||||
};
|
||||
const testSnapshot = () => {
|
||||
test('snapshot', () => {
|
||||
expect(render()).toMatchSnapshot();
|
||||
expect(el).toMatchSnapshot();
|
||||
});
|
||||
};
|
||||
describe('collapsed', () => {
|
||||
testColumns();
|
||||
testSnapshot();
|
||||
describe('sidebar showing', () => {
|
||||
beforeEach(() => {
|
||||
hooks.useDashboardLayoutData.mockReturnValueOnce({ ...hookProps, sidebarShowing: true });
|
||||
});
|
||||
testColumns();
|
||||
testSnapshot();
|
||||
testSidebarLayout();
|
||||
});
|
||||
describe('sidebar not showing', () => {
|
||||
testColumns();
|
||||
testSnapshot();
|
||||
testNoSidebarLayout();
|
||||
});
|
||||
it('does not show spacer component above widget sidebar', () => {
|
||||
const columns = render().find(Col);
|
||||
const columns = el.find(Col);
|
||||
expect(columns.at(1).find('h2').length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('not collapsed', () => {
|
||||
beforeEach(() => { hooks.useIsDashboardCollapsed.mockReturnValueOnce(false); });
|
||||
testColumns();
|
||||
testSnapshot();
|
||||
it('shows a blank (nbsp) h2 spacer component above widget sidebar', () => {
|
||||
const columns = render().find(Col);
|
||||
// nonbreaking space equivalent
|
||||
expect(columns.at(1).find('h2').text()).toEqual('\xA0');
|
||||
const testWidgetSpacing = () => {
|
||||
it('shows a blank (nbsp) h2 spacer component above widget sidebar', () => {
|
||||
const columns = el.find(Col);
|
||||
// nonbreaking space equivalent
|
||||
expect(columns.at(1).find('h2').text()).toEqual('\xA0');
|
||||
});
|
||||
};
|
||||
describe('sidebar showing', () => {
|
||||
beforeEach(() => {
|
||||
hooks.useDashboardLayoutData.mockReturnValueOnce({
|
||||
...hookProps,
|
||||
isCollapsed: false,
|
||||
sidebarShowing: true,
|
||||
});
|
||||
});
|
||||
testColumns();
|
||||
testSnapshot();
|
||||
testSidebarLayout();
|
||||
testWidgetSpacing();
|
||||
});
|
||||
describe('sidebar not showing', () => {
|
||||
beforeEach(() => {
|
||||
hooks.useDashboardLayoutData.mockReturnValueOnce({ ...hookProps, isCollapsed: false });
|
||||
});
|
||||
testColumns();
|
||||
testSnapshot();
|
||||
testNoSidebarLayout();
|
||||
testWidgetSpacing();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,57 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DashboardLayout collapsed snapshot 1`] = `
|
||||
exports[`DashboardLayout collapsed sidebar not showing snapshot 1`] = `
|
||||
<Container
|
||||
fluid={true}
|
||||
size="xl"
|
||||
>
|
||||
<Row>
|
||||
<Col
|
||||
className="course-list-column"
|
||||
lg={
|
||||
Object {
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
}
|
||||
xl={
|
||||
Object {
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
}
|
||||
>
|
||||
test-children
|
||||
</Col>
|
||||
<Col
|
||||
className="sidebar-column"
|
||||
lg={
|
||||
Object {
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
}
|
||||
xl={
|
||||
Object {
|
||||
"offset": 0,
|
||||
"span": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<mockConstructor
|
||||
setSidebarShowing={[MockFunction hooks.setSidebarShowing]}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col>
|
||||
<WidgetFooter />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
`;
|
||||
|
||||
exports[`DashboardLayout collapsed sidebar showing snapshot 1`] = `
|
||||
<Container
|
||||
fluid={true}
|
||||
size="xl"
|
||||
@@ -38,13 +89,76 @@ exports[`DashboardLayout collapsed snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
test-sidebar-content
|
||||
<mockConstructor
|
||||
setSidebarShowing={[MockFunction hooks.setSidebarShowing]}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col>
|
||||
<WidgetFooter />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
`;
|
||||
|
||||
exports[`DashboardLayout not collapsed snapshot 1`] = `
|
||||
exports[`DashboardLayout not collapsed sidebar not showing snapshot 1`] = `
|
||||
<Container
|
||||
fluid={true}
|
||||
size="xl"
|
||||
>
|
||||
<Row>
|
||||
<Col
|
||||
className="course-list-column"
|
||||
lg={
|
||||
Object {
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
}
|
||||
xl={
|
||||
Object {
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
}
|
||||
>
|
||||
test-children
|
||||
</Col>
|
||||
<Col
|
||||
className="sidebar-column"
|
||||
lg={
|
||||
Object {
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
}
|
||||
xl={
|
||||
Object {
|
||||
"offset": 0,
|
||||
"span": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<h2
|
||||
className="course-list-title"
|
||||
>
|
||||
|
||||
</h2>
|
||||
<mockConstructor
|
||||
setSidebarShowing={[MockFunction hooks.setSidebarShowing]}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col>
|
||||
<WidgetFooter />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
`;
|
||||
|
||||
exports[`DashboardLayout not collapsed sidebar showing snapshot 1`] = `
|
||||
<Container
|
||||
fluid={true}
|
||||
size="xl"
|
||||
@@ -87,7 +201,14 @@ exports[`DashboardLayout not collapsed snapshot 1`] = `
|
||||
>
|
||||
|
||||
</h2>
|
||||
test-sidebar-content
|
||||
<mockConstructor
|
||||
setSidebarShowing={[MockFunction hooks.setSidebarShowing]}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col>
|
||||
<WidgetFooter />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
|
||||
@@ -15,7 +15,7 @@ exports[`Dashboard snapshots courses loaded, show select session modal, no avail
|
||||
id="dashboard-content"
|
||||
>
|
||||
<DashboardLayout
|
||||
sidebar={<LoadedWidgetSidebar />}
|
||||
sidebar="LoadedWidgetSidebar"
|
||||
>
|
||||
<CourseList />
|
||||
</DashboardLayout>
|
||||
@@ -56,7 +56,7 @@ exports[`Dashboard snapshots there are no courses, there ARE available dashboard
|
||||
id="dashboard-content"
|
||||
>
|
||||
<DashboardLayout
|
||||
sidebar={<NoCoursesWidgetSidebar />}
|
||||
sidebar="NoCoursesWidgetSidebar"
|
||||
>
|
||||
<CourseList />
|
||||
</DashboardLayout>
|
||||
|
||||
@@ -2,13 +2,14 @@ import React from 'react';
|
||||
import { useWindowSize, breakpoints } from '@edx/paragon';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { apiHooks } from 'hooks';
|
||||
import { StrictDict } from 'utils';
|
||||
|
||||
import appMessages from 'messages';
|
||||
import * as module from './hooks';
|
||||
|
||||
export const useIsDashboardCollapsed = () => {
|
||||
const { width } = useWindowSize();
|
||||
return width < breakpoints.large.maxWidth;
|
||||
};
|
||||
export const state = StrictDict({
|
||||
sidebarShowing: (val) => React.useState(val), // eslint-disable-line
|
||||
});
|
||||
|
||||
export const useInitializeDashboard = () => {
|
||||
const initialize = apiHooks.useInitializeApp();
|
||||
@@ -23,8 +24,18 @@ export const useDashboardMessages = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useDashboardLayoutData = () => {
|
||||
const { width } = useWindowSize();
|
||||
const [sidebarShowing, setSidebarShowing] = module.state.sidebarShowing(false);
|
||||
return {
|
||||
isDashboardCollapsed: width < breakpoints.large.maxWidth,
|
||||
sidebarShowing,
|
||||
setSidebarShowing,
|
||||
};
|
||||
};
|
||||
|
||||
export default {
|
||||
useIsDashboardCollapsed,
|
||||
useDashboardLayoutData,
|
||||
useInitializeDashboard,
|
||||
useDashboardMessages,
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useWindowSize, breakpoints } from '@edx/paragon';
|
||||
|
||||
import { apiHooks } from 'hooks';
|
||||
import { MockUseState } from 'testUtils';
|
||||
|
||||
import appMessages from 'messages';
|
||||
import * as hooks from './hooks';
|
||||
@@ -19,8 +20,12 @@ jest.mock('hooks', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const state = new MockUseState(hooks);
|
||||
|
||||
const initializeApp = jest.fn();
|
||||
apiHooks.useInitializeApp.mockReturnValue(initializeApp);
|
||||
useWindowSize.mockReturnValue({ width: 20 });
|
||||
breakpoints.large = { maxWidth: 30 };
|
||||
describe('CourseCard hooks', () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
@@ -28,15 +33,32 @@ describe('CourseCard hooks', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('useIsDashboardCollapsed', () => {
|
||||
it('returns true iff windowSize width is below the xl breakpoint', () => {
|
||||
useWindowSize.mockReturnValueOnce({ width: 20 });
|
||||
breakpoints.large = { maxWidth: 30 };
|
||||
expect(hooks.useIsDashboardCollapsed()).toEqual(true);
|
||||
useWindowSize.mockReturnValueOnce({ width: 40 });
|
||||
expect(hooks.useIsDashboardCollapsed()).toEqual(false);
|
||||
useWindowSize.mockReturnValueOnce({ width: 40 });
|
||||
expect(hooks.useIsDashboardCollapsed()).toEqual(false);
|
||||
describe('state fields', () => {
|
||||
state.testGetter(state.keys.sidebarShowing);
|
||||
});
|
||||
|
||||
describe('useDashboardLayoutData', () => {
|
||||
beforeEach(() => { state.mock(); });
|
||||
describe('behavior', () => {
|
||||
it('initializes sidebarShowing to default false value', () => {
|
||||
hooks.useDashboardLayoutData();
|
||||
state.expectInitializedWith(state.keys.sidebarShowing, false);
|
||||
});
|
||||
});
|
||||
describe('output', () => {
|
||||
describe('isDashboardCollapsed', () => {
|
||||
it('returns true iff windowSize width is below the xl breakpoint', () => {
|
||||
expect(hooks.useDashboardLayoutData().isDashboardCollapsed).toEqual(true);
|
||||
useWindowSize.mockReturnValueOnce({ width: 40 });
|
||||
expect(hooks.useDashboardLayoutData().isDashboardCollapsed).toEqual(false);
|
||||
});
|
||||
});
|
||||
it('forwards sidebarShowing and setSidebarShowing from state hook', () => {
|
||||
const hook = hooks.useDashboardLayoutData();
|
||||
const { sidebarShowing, setSidebarShowing } = hook;
|
||||
expect(sidebarShowing).toEqual(state.stateVals.sidebarShowing);
|
||||
expect(setSidebarShowing).toEqual(state.setState.sidebarShowing);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('useInitializeDashboard', () => {
|
||||
|
||||
@@ -21,6 +21,7 @@ export const Dashboard = () => {
|
||||
const hasAvailableDashboards = reduxHooks.useHasAvailableDashboards();
|
||||
const initIsPending = reduxHooks.useRequestIsPending(RequestKeys.initialize);
|
||||
const showSelectSessionModal = reduxHooks.useShowSelectSessionModal();
|
||||
|
||||
return (
|
||||
<div id="dashboard-container" className="d-flex flex-column p-2 pt-0">
|
||||
<h1 className="sr-only">{pageTitle}</h1>
|
||||
@@ -34,7 +35,7 @@ export const Dashboard = () => {
|
||||
{initIsPending
|
||||
? (<LoadingView />)
|
||||
: (
|
||||
<DashboardLayout sidebar={hasCourses ? <LoadedSidebar /> : <NoCoursesSidebar />}>
|
||||
<DashboardLayout sidebar={hasCourses ? LoadedSidebar : NoCoursesSidebar}>
|
||||
<CourseList />
|
||||
</DashboardLayout>
|
||||
)}
|
||||
|
||||
@@ -116,7 +116,7 @@ describe('Dashboard', () => {
|
||||
showSelectSessionModal: true,
|
||||
},
|
||||
content: ['LoadedView', (
|
||||
<DashboardLayout sidebar={<LoadedWidgetSidebar />}><CourseList /></DashboardLayout>
|
||||
<DashboardLayout sidebar={LoadedWidgetSidebar}><CourseList /></DashboardLayout>
|
||||
)],
|
||||
showEnterpriseModal: false,
|
||||
showSelectSessionModal: true,
|
||||
@@ -132,7 +132,7 @@ describe('Dashboard', () => {
|
||||
showSelectSessionModal: false,
|
||||
},
|
||||
content: ['Dashboard layout with no courses sidebar and content', (
|
||||
<DashboardLayout sidebar={<NoCoursesWidgetSidebar />}><CourseList /></DashboardLayout>
|
||||
<DashboardLayout sidebar={NoCoursesWidgetSidebar}><CourseList /></DashboardLayout>
|
||||
)],
|
||||
showEnterpriseModal: true,
|
||||
showSelectSessionModal: false,
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from 'react';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { configuration } from '../../config';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
@@ -14,7 +15,7 @@ export const BrandLogo = () => {
|
||||
<a href={dashboard?.url || '/'} className="mx-auto">
|
||||
<img
|
||||
className="logo py-3"
|
||||
src="https://edx-cdn.org/v3/prod/logo.svg"
|
||||
src={configuration.LOGO_URL}
|
||||
alt={formatMessage(messages.logoAltText)}
|
||||
/>
|
||||
</a>
|
||||
|
||||
@@ -4,14 +4,14 @@ import PropTypes from 'prop-types';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
|
||||
import { Button, Badge } from '@edx/paragon';
|
||||
|
||||
import WidgetNavbar from 'containers/WidgetContainers/WidgetNavbar';
|
||||
import urls from 'data/services/lms/urls';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { COLLAPSED_NAVBAR } from 'widgets/RecommendationsPaintedDoorBtn/constants';
|
||||
|
||||
import { findCoursesNavDropdownClicked } from '../hooks';
|
||||
|
||||
import messages from '../messages';
|
||||
|
||||
export const CollapseMenuBody = ({ isOpen }) => {
|
||||
@@ -21,7 +21,7 @@ export const CollapseMenuBody = ({ isOpen }) => {
|
||||
const dashboard = reduxHooks.useEnterpriseDashboardData();
|
||||
const { courseSearchUrl } = reduxHooks.usePlatformSettingsData();
|
||||
|
||||
const exploreCoursesClick = findCoursesNavDropdownClicked(courseSearchUrl);
|
||||
const exploreCoursesClick = findCoursesNavDropdownClicked(urls.baseAppUrl(courseSearchUrl));
|
||||
|
||||
return (
|
||||
isOpen && (
|
||||
@@ -34,12 +34,13 @@ export const CollapseMenuBody = ({ isOpen }) => {
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href={courseSearchUrl}
|
||||
href={urls.baseAppUrl(courseSearchUrl)}
|
||||
variant="inverse-primary"
|
||||
onClick={exploreCoursesClick}
|
||||
>
|
||||
{formatMessage(messages.discoverNew)}
|
||||
</Button>
|
||||
<WidgetNavbar placement={COLLAPSED_NAVBAR} />
|
||||
<Button as="a" href={getConfig().SUPPORT_URL} variant="inverse-primary">
|
||||
{formatMessage(messages.help)}
|
||||
</Button>
|
||||
|
||||
@@ -17,7 +17,7 @@ jest.mock('hooks', () => ({
|
||||
url: 'url',
|
||||
}),
|
||||
usePlatformSettingsData: () => ({
|
||||
courseSearchUrl: 'courseSearchUrl',
|
||||
courseSearchUrl: '/courseSearchUrl',
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -20,12 +20,15 @@ exports[`CollapseMenuBody render 1`] = `
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href="courseSearchUrl"
|
||||
onClick={[MockFunction findCoursesNavDropdownClicked("courseSearchUrl")]}
|
||||
href="http://localhost:18000/courseSearchUrl"
|
||||
onClick={[MockFunction findCoursesNavDropdownClicked("http://localhost:18000/courseSearchUrl")]}
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Discover New
|
||||
</Button>
|
||||
<WidgetNavbar
|
||||
placement="collapsedNavbar"
|
||||
/>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/support"
|
||||
@@ -86,12 +89,15 @@ exports[`CollapseMenuBody render unauthenticated 1`] = `
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href="courseSearchUrl"
|
||||
onClick={[MockFunction findCoursesNavDropdownClicked("courseSearchUrl")]}
|
||||
href="http://localhost:18000/courseSearchUrl"
|
||||
onClick={[MockFunction findCoursesNavDropdownClicked("http://localhost:18000/courseSearchUrl")]}
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Discover New
|
||||
</Button>
|
||||
<WidgetNavbar
|
||||
placement="collapsedNavbar"
|
||||
/>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/support"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Menu, Close } from '@edx/paragon/icons';
|
||||
import { MenuIcon, Close } from '@edx/paragon/icons';
|
||||
import { IconButton, Icon } from '@edx/paragon';
|
||||
|
||||
import { useLearnerDashboardHeaderData, useIsCollapsed } from '../hooks';
|
||||
@@ -23,7 +23,7 @@ export const CollapsedHeader = () => {
|
||||
<IconButton
|
||||
invertColors
|
||||
isActive
|
||||
src={isOpen ? Close : Menu}
|
||||
src={isOpen ? Close : MenuIcon}
|
||||
iconAs={Icon}
|
||||
alt={
|
||||
isOpen
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useIsCollapsed } from '../hooks';
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
getConfig: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-platform/react', () => ({
|
||||
AppContext: {
|
||||
authenticatedUser: {
|
||||
@@ -17,11 +18,13 @@ jest.mock('@edx/frontend-platform/react', () => ({
|
||||
},
|
||||
},
|
||||
}));
|
||||
const COURSE_SEARCH_URL = 'test-course-search-url';
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useEnterpriseDashboardData: jest.fn(),
|
||||
usePlatformSettingsData: jest.fn(() => ({
|
||||
courseSearchUrl: 'test-course-search-url',
|
||||
courseSearchUrl: COURSE_SEARCH_URL,
|
||||
})),
|
||||
},
|
||||
}));
|
||||
@@ -30,6 +33,11 @@ jest.mock('../hooks', () => ({
|
||||
findCoursesNavDropdownClicked: (href) => jest.fn().mockName(`findCoursesNavDropdownClicked('${href}')`),
|
||||
}));
|
||||
|
||||
jest.mock('data/services/lms/urls', () => ({
|
||||
baseAppUrl: (url) => (url),
|
||||
programsUrl: 'http://localhost:18000/dashboard/programs',
|
||||
}));
|
||||
|
||||
const config = {
|
||||
ACCOUNT_PROFILE_URL: 'http://account-profile-url.test',
|
||||
ACCOUNT_SETTINGS_URL: 'http://account-settings-url.test',
|
||||
@@ -37,6 +45,7 @@ const config = {
|
||||
ORDER_HISTORY_URL: 'http://order-history-url.test',
|
||||
SUPPORT_URL: 'http://localhost:18000/support',
|
||||
CAREER_LINK_URL: 'http://localhost:18000/career',
|
||||
LMS_BASE_URL: 'http:/localhost:18000',
|
||||
};
|
||||
getConfig.mockReturnValue(config);
|
||||
|
||||
|
||||
@@ -27,12 +27,15 @@ exports[`ExpandedHeader render 1`] = `
|
||||
<Button
|
||||
as="a"
|
||||
className="p-4"
|
||||
href="courseSearchUrl"
|
||||
onClick={[MockFunction findCoursesNavClicked("courseSearchUrl")]}
|
||||
href="http://localhost:18000/courseSearchUrl"
|
||||
onClick={[MockFunction findCoursesNavClicked("http://localhost:18000/courseSearchUrl")]}
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Discover New
|
||||
</Button>
|
||||
<WidgetNavbar
|
||||
placement="expendedNavbar"
|
||||
/>
|
||||
<span
|
||||
className="flex-grow-1"
|
||||
/>
|
||||
|
||||
@@ -4,11 +4,12 @@ import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Button } from '@edx/paragon';
|
||||
|
||||
import WidgetNavbar from 'containers/WidgetContainers/WidgetNavbar';
|
||||
import urls from 'data/services/lms/urls';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { EXPANDED_NAVBAR } from 'widgets/RecommendationsPaintedDoorBtn/constants';
|
||||
|
||||
import AuthenticatedUserDropdown from './AuthenticatedUserDropdown';
|
||||
|
||||
import { useIsCollapsed, findCoursesNavClicked } from '../hooks';
|
||||
import messages from '../messages';
|
||||
import BrandLogo from '../BrandLogo';
|
||||
@@ -18,7 +19,7 @@ export const ExpandedHeader = () => {
|
||||
const { courseSearchUrl } = reduxHooks.usePlatformSettingsData();
|
||||
const isCollapsed = useIsCollapsed();
|
||||
|
||||
const exploreCoursesClick = findCoursesNavClicked(courseSearchUrl);
|
||||
const exploreCoursesClick = findCoursesNavClicked(urls.baseAppUrl(courseSearchUrl));
|
||||
|
||||
return (
|
||||
!isCollapsed && (
|
||||
@@ -44,13 +45,14 @@ export const ExpandedHeader = () => {
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href={courseSearchUrl}
|
||||
href={urls.baseAppUrl(courseSearchUrl)}
|
||||
variant="inverse-primary"
|
||||
className="p-4"
|
||||
onClick={exploreCoursesClick}
|
||||
>
|
||||
{formatMessage(messages.discoverNew)}
|
||||
</Button>
|
||||
<WidgetNavbar placement={EXPANDED_NAVBAR} />
|
||||
<span className="flex-grow-1" />
|
||||
<Button
|
||||
as="a"
|
||||
|
||||
@@ -6,12 +6,13 @@ import { useIsCollapsed } from '../hooks';
|
||||
|
||||
jest.mock('data/services/lms/urls', () => ({
|
||||
programsUrl: 'programsUrl',
|
||||
baseAppUrl: url => (`http://localhost:18000${url}`),
|
||||
}));
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
usePlatformSettingsData: () => ({
|
||||
courseSearchUrl: 'courseSearchUrl',
|
||||
courseSearchUrl: '/courseSearchUrl',
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -8,7 +8,7 @@ exports[`BrandLogo dashboard defined 1`] = `
|
||||
<img
|
||||
alt="edX, Inc. Dashboard"
|
||||
className="logo py-3"
|
||||
src="https://edx-cdn.org/v3/prod/logo.svg"
|
||||
src="https://edx-cdn.org/v3/default/logo.svg"
|
||||
/>
|
||||
</a>
|
||||
`;
|
||||
@@ -21,7 +21,7 @@ exports[`BrandLogo dashboard undefined 1`] = `
|
||||
<img
|
||||
alt="edX, Inc. Dashboard"
|
||||
className="logo py-3"
|
||||
src="https://edx-cdn.org/v3/prod/logo.svg"
|
||||
src="https://edx-cdn.org/v3/default/logo.svg"
|
||||
/>
|
||||
</a>
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AppWrapper WidgetContainer component output no experiments are active snapshot 1`] = `
|
||||
<div>
|
||||
This is some
|
||||
<b>
|
||||
test
|
||||
</b>
|
||||
|
||||
<i>
|
||||
content
|
||||
</i>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`AppWrapper WidgetContainer component output painted door experiment is active (08/23) snapshot 1`] = `
|
||||
<PaintedDoorExperimentProvider>
|
||||
<div>
|
||||
This is some
|
||||
<b>
|
||||
test
|
||||
</b>
|
||||
|
||||
<i>
|
||||
content
|
||||
</i>
|
||||
</div>
|
||||
</PaintedDoorExperimentProvider>
|
||||
`;
|
||||
23
src/containers/WidgetContainers/AppWrapper/index.jsx
Normal file
23
src/containers/WidgetContainers/AppWrapper/index.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import PaintedDoorExperimentProvider from 'widgets/RecommendationsPaintedDoorBtn/PaintedDoorExperimentContext';
|
||||
|
||||
export const AppWrapper = ({
|
||||
children,
|
||||
}) => {
|
||||
console.log(`process.env.EXPERIMENT_08_23_VAN_PAINTED_DOOR = ${Boolean(process.env.EXPERIMENT_08_23_VAN_PAINTED_DOOR)}`);
|
||||
return (
|
||||
<PaintedDoorExperimentProvider>
|
||||
{children}
|
||||
</PaintedDoorExperimentProvider>
|
||||
);
|
||||
};
|
||||
AppWrapper.propTypes = {
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.node,
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
]).isRequired,
|
||||
};
|
||||
|
||||
export default AppWrapper;
|
||||
56
src/containers/WidgetContainers/AppWrapper/index.test.jsx
Normal file
56
src/containers/WidgetContainers/AppWrapper/index.test.jsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import PaintedDoorExperimentProvider from 'widgets/RecommendationsPaintedDoorBtn/PaintedDoorExperimentContext';
|
||||
|
||||
import AppWrapper from '.';
|
||||
|
||||
jest.mock(
|
||||
'widgets/RecommendationsPaintedDoorBtn/PaintedDoorExperimentContext',
|
||||
() => 'PaintedDoorExperimentProvider',
|
||||
);
|
||||
|
||||
let el;
|
||||
|
||||
const children = (<div>This is some <b>test</b> <i>content</i></div>);
|
||||
|
||||
const render = () => {
|
||||
el = shallow(<AppWrapper>{children}</AppWrapper>);
|
||||
};
|
||||
|
||||
const mockAndRenderForBlock = (newVal) => {
|
||||
const oldVal = process.env;
|
||||
beforeEach(() => {
|
||||
process.env = { ...oldVal, ...newVal };
|
||||
render();
|
||||
});
|
||||
afterEach(() => {
|
||||
process.env = oldVal;
|
||||
render();
|
||||
});
|
||||
};
|
||||
|
||||
describe('AppWrapper WidgetContainer component', () => {
|
||||
describe('output', () => {
|
||||
describe('painted door experiment is active (08/23)', () => {
|
||||
mockAndRenderForBlock({ EXPERIMENT_08_23_VAN_PAINTED_DOOR: true });
|
||||
test('snapshot', () => {
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
it('renders children wrapped in PaintedDoorExperimentProvider', () => {
|
||||
const control = el.instance.findByType(PaintedDoorExperimentProvider)[0];
|
||||
expect(el.instance).toEqual(control);
|
||||
});
|
||||
});
|
||||
describe('no experiments are active', () => {
|
||||
mockAndRenderForBlock({ EXPERIMENT_08_23_VAN_PAINTED_DOOR: false });
|
||||
test('snapshot', () => {
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
it('renders children wrapped in PaintedDoorExperimentProvider', () => {
|
||||
expect(el.instance.matches(shallow(children))).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,7 @@ exports[`WidgetSidebar snapshots default 1`] = `
|
||||
className="widget-sidebar"
|
||||
>
|
||||
<div
|
||||
className="d-flex"
|
||||
className="d-flex flex-column"
|
||||
>
|
||||
<RecommendationsPanel />
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import RecommendationsPanel from 'widgets/RecommendationsPanel';
|
||||
import hooks from 'widgets/ProductRecommendations/hooks';
|
||||
|
||||
export const WidgetSidebar = () => (
|
||||
<div className="widget-sidebar">
|
||||
<div className="d-flex">
|
||||
<RecommendationsPanel />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
export const WidgetSidebar = ({ setSidebarShowing }) => {
|
||||
const { inRecommendationsVariant, isExperimentActive } = hooks.useShowRecommendationsFooter();
|
||||
|
||||
if (!inRecommendationsVariant && isExperimentActive) {
|
||||
setSidebarShowing(true);
|
||||
|
||||
return (
|
||||
<div className="widget-sidebar">
|
||||
<div className="d-flex flex-column">
|
||||
<RecommendationsPanel />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
WidgetSidebar.propTypes = {
|
||||
setSidebarShowing: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default WidgetSidebar;
|
||||
|
||||
@@ -1,14 +1,49 @@
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import hooks from 'widgets/ProductRecommendations/hooks';
|
||||
import { mockFooterRecommendationsHook } from 'widgets/ProductRecommendations/testData';
|
||||
import WidgetSidebar from '.';
|
||||
|
||||
jest.mock('widgets/LookingForChallengeWidget', () => 'LookingForChallengeWidget');
|
||||
jest.mock('widgets/ProductRecommendations/hooks', () => ({
|
||||
useShowRecommendationsFooter: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('WidgetSidebar', () => {
|
||||
beforeEach(() => jest.resetAllMocks());
|
||||
const props = {
|
||||
setSidebarShowing: jest.fn(),
|
||||
};
|
||||
|
||||
describe('snapshots', () => {
|
||||
test('default', () => {
|
||||
const wrapper = shallow(<WidgetSidebar />);
|
||||
hooks.useShowRecommendationsFooter.mockReturnValueOnce(
|
||||
mockFooterRecommendationsHook.activeControl,
|
||||
);
|
||||
const wrapper = shallow(<WidgetSidebar {...props} />);
|
||||
|
||||
expect(props.setSidebarShowing).toHaveBeenCalledWith(true);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
test('is hidden when the has the default values', () => {
|
||||
hooks.useShowRecommendationsFooter.mockReturnValueOnce(
|
||||
mockFooterRecommendationsHook.default,
|
||||
);
|
||||
const wrapper = shallow(<WidgetSidebar {...props} />);
|
||||
|
||||
expect(props.setSidebarShowing).not.toHaveBeenCalled();
|
||||
expect(wrapper.type()).toBeNull();
|
||||
});
|
||||
|
||||
test('is hidden when the has the treatment values', () => {
|
||||
hooks.useShowRecommendationsFooter.mockReturnValueOnce(
|
||||
mockFooterRecommendationsHook.activeTreatment,
|
||||
);
|
||||
const wrapper = shallow(<WidgetSidebar {...props} />);
|
||||
|
||||
expect(props.setSidebarShowing).not.toHaveBeenCalled();
|
||||
expect(wrapper.type()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import RecommendationsPanel from 'widgets/RecommendationsPanel';
|
||||
import hooks from 'widgets/ProductRecommendations/hooks';
|
||||
|
||||
export const WidgetSidebar = () => (
|
||||
<div className="widget-sidebar px-2">
|
||||
<div className="d-flex">
|
||||
<RecommendationsPanel />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
export const WidgetSidebar = ({ setSidebarShowing }) => {
|
||||
const { inRecommendationsVariant, isExperimentActive } = hooks.useShowRecommendationsFooter();
|
||||
|
||||
if (!inRecommendationsVariant && isExperimentActive) {
|
||||
setSidebarShowing(true);
|
||||
|
||||
return (
|
||||
<div className="widget-sidebar px-2">
|
||||
<div className="d-flex">
|
||||
<RecommendationsPanel />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
WidgetSidebar.propTypes = {
|
||||
setSidebarShowing: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default WidgetSidebar;
|
||||
|
||||
@@ -1,14 +1,49 @@
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import hooks from 'widgets/ProductRecommendations/hooks';
|
||||
import { mockFooterRecommendationsHook } from 'widgets/ProductRecommendations/testData';
|
||||
import WidgetSidebar from '.';
|
||||
|
||||
jest.mock('widgets/LookingForChallengeWidget', () => 'LookingForChallengeWidget');
|
||||
jest.mock('widgets/ProductRecommendations/hooks', () => ({
|
||||
useShowRecommendationsFooter: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('WidgetSidebar', () => {
|
||||
beforeEach(() => jest.resetAllMocks());
|
||||
const props = {
|
||||
setSidebarShowing: jest.fn(),
|
||||
};
|
||||
|
||||
describe('snapshots', () => {
|
||||
test('default', () => {
|
||||
const wrapper = shallow(<WidgetSidebar />);
|
||||
hooks.useShowRecommendationsFooter.mockReturnValueOnce(
|
||||
mockFooterRecommendationsHook.activeControl,
|
||||
);
|
||||
const wrapper = shallow(<WidgetSidebar {...props} />);
|
||||
|
||||
expect(props.setSidebarShowing).toHaveBeenCalledWith(true);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
test('is hidden when the has the default values', () => {
|
||||
hooks.useShowRecommendationsFooter.mockReturnValueOnce(
|
||||
mockFooterRecommendationsHook.default,
|
||||
);
|
||||
const wrapper = shallow(<WidgetSidebar {...props} />);
|
||||
|
||||
expect(props.setSidebarShowing).not.toHaveBeenCalled();
|
||||
expect(wrapper.type()).toBeNull();
|
||||
});
|
||||
|
||||
test('is hidden when the has the treatment values', () => {
|
||||
hooks.useShowRecommendationsFooter.mockReturnValueOnce(
|
||||
mockFooterRecommendationsHook.activeTreatment,
|
||||
);
|
||||
const wrapper = shallow(<WidgetSidebar {...props} />);
|
||||
|
||||
expect(props.setSidebarShowing).not.toHaveBeenCalled();
|
||||
expect(wrapper.type()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`WidgetFooter snapshots default 1`] = `
|
||||
<div
|
||||
className="widget-footer"
|
||||
>
|
||||
<ProductRecommendations />
|
||||
</div>
|
||||
`;
|
||||
21
src/containers/WidgetContainers/WidgetFooter/index.jsx
Normal file
21
src/containers/WidgetContainers/WidgetFooter/index.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
|
||||
import ProductRecommendations from 'widgets/ProductRecommendations';
|
||||
import hooks from 'widgets/ProductRecommendations/hooks';
|
||||
|
||||
export const WidgetFooter = () => {
|
||||
hooks.useActivateRecommendationsExperiment();
|
||||
const { inRecommendationsVariant, isExperimentActive } = hooks.useShowRecommendationsFooter();
|
||||
|
||||
if (inRecommendationsVariant && isExperimentActive) {
|
||||
return (
|
||||
<div className="widget-footer">
|
||||
<ProductRecommendations />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default WidgetFooter;
|
||||
45
src/containers/WidgetContainers/WidgetFooter/index.test.jsx
Normal file
45
src/containers/WidgetContainers/WidgetFooter/index.test.jsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import hooks from 'widgets/ProductRecommendations/hooks';
|
||||
import { mockFooterRecommendationsHook } from 'widgets/ProductRecommendations/testData';
|
||||
import WidgetFooter from '.';
|
||||
|
||||
jest.mock('widgets/LookingForChallengeWidget', () => 'LookingForChallengeWidget');
|
||||
jest.mock('widgets/ProductRecommendations/hooks', () => ({
|
||||
useActivateRecommendationsExperiment: jest.fn(),
|
||||
useShowRecommendationsFooter: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('WidgetFooter', () => {
|
||||
describe('snapshots', () => {
|
||||
test('default', () => {
|
||||
hooks.useShowRecommendationsFooter.mockReturnValueOnce(
|
||||
mockFooterRecommendationsHook.activeTreatment,
|
||||
);
|
||||
const wrapper = shallow(<WidgetFooter />);
|
||||
|
||||
expect(hooks.useActivateRecommendationsExperiment).toHaveBeenCalled();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
test('is hidden when the experiment has the default values', () => {
|
||||
hooks.useShowRecommendationsFooter.mockReturnValueOnce(
|
||||
mockFooterRecommendationsHook.default,
|
||||
);
|
||||
const wrapper = shallow(<WidgetFooter />);
|
||||
|
||||
expect(hooks.useActivateRecommendationsExperiment).toHaveBeenCalled();
|
||||
expect(wrapper.type()).toBeNull();
|
||||
});
|
||||
|
||||
test('is hidden when the experiment has the control values', () => {
|
||||
hooks.useShowRecommendationsFooter.mockReturnValueOnce(
|
||||
mockFooterRecommendationsHook.activeControl,
|
||||
);
|
||||
const wrapper = shallow(<WidgetFooter />);
|
||||
|
||||
expect(hooks.useActivateRecommendationsExperiment).toHaveBeenCalled();
|
||||
expect(wrapper.type()).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`WidgetNavbar snapshots default 1`] = `
|
||||
<RecommendationsPaintedDoorBtn
|
||||
experimentVariation=""
|
||||
placement="expendedNavbar"
|
||||
/>
|
||||
`;
|
||||
29
src/containers/WidgetContainers/WidgetNavbar/index.jsx
Normal file
29
src/containers/WidgetContainers/WidgetNavbar/index.jsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import RecommendationsPaintedDoorBtn from 'widgets/RecommendationsPaintedDoorBtn';
|
||||
import { COLLAPSED_NAVBAR, EXPANDED_NAVBAR } from 'widgets/RecommendationsPaintedDoorBtn/constants';
|
||||
import {
|
||||
usePaintedDoorExperimentContext,
|
||||
} from 'widgets/RecommendationsPaintedDoorBtn/PaintedDoorExperimentContext';
|
||||
|
||||
export const WidgetNavbar = ({ placement }) => {
|
||||
const {
|
||||
experimentVariation,
|
||||
isPaintedDoorNavbarBtnVariation,
|
||||
experimentLoading,
|
||||
} = usePaintedDoorExperimentContext();
|
||||
|
||||
if (!experimentLoading && isPaintedDoorNavbarBtnVariation) {
|
||||
return (
|
||||
<RecommendationsPaintedDoorBtn placement={placement} experimentVariation={experimentVariation} />
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
WidgetNavbar.propTypes = {
|
||||
placement: PropTypes.oneOf([COLLAPSED_NAVBAR, EXPANDED_NAVBAR]).isRequired,
|
||||
};
|
||||
|
||||
export default WidgetNavbar;
|
||||
65
src/containers/WidgetContainers/WidgetNavbar/index.test.jsx
Normal file
65
src/containers/WidgetContainers/WidgetNavbar/index.test.jsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { shallow } from 'enzyme';
|
||||
import {
|
||||
usePaintedDoorExperimentContext,
|
||||
} from '../../../widgets/RecommendationsPaintedDoorBtn/PaintedDoorExperimentContext';
|
||||
import WidgetNavbar from './index';
|
||||
import { EXPANDED_NAVBAR } from '../../../widgets/RecommendationsPaintedDoorBtn/constants';
|
||||
import RecommendationsPaintedDoorBtn from '../../../widgets/RecommendationsPaintedDoorBtn';
|
||||
|
||||
jest.mock('widgets/RecommendationsPaintedDoorBtn/PaintedDoorExperimentContext', () => ({
|
||||
usePaintedDoorExperimentContext: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('WidgetNavbar', () => {
|
||||
let mockExperimentContext = {
|
||||
experimentVariation: '',
|
||||
isPaintedDoorNavbarBtnVariation: true,
|
||||
experimentLoading: false,
|
||||
};
|
||||
const props = {
|
||||
placement: EXPANDED_NAVBAR,
|
||||
};
|
||||
|
||||
describe('snapshots', () => {
|
||||
test('default', () => {
|
||||
usePaintedDoorExperimentContext.mockReturnValueOnce(mockExperimentContext);
|
||||
const wrapper = shallow(<WidgetNavbar {...props} />);
|
||||
|
||||
expect(usePaintedDoorExperimentContext).toHaveBeenCalled();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
test('renders button if user in navbar variation', () => {
|
||||
usePaintedDoorExperimentContext.mockReturnValueOnce(mockExperimentContext);
|
||||
const wrapper = shallow(<WidgetNavbar {...props} />);
|
||||
|
||||
expect(usePaintedDoorExperimentContext).toHaveBeenCalled();
|
||||
expect(wrapper.type()).toBe(RecommendationsPaintedDoorBtn);
|
||||
});
|
||||
|
||||
test('renders nothing if user in not in navbar variation', () => {
|
||||
mockExperimentContext = {
|
||||
...mockExperimentContext,
|
||||
isPaintedDoorNavbarBtnVariation: false,
|
||||
};
|
||||
usePaintedDoorExperimentContext.mockReturnValueOnce(mockExperimentContext);
|
||||
const wrapper = shallow(<WidgetNavbar {...props} />);
|
||||
|
||||
expect(usePaintedDoorExperimentContext).toHaveBeenCalled();
|
||||
expect(wrapper.type()).toBeNull();
|
||||
});
|
||||
|
||||
test('renders nothing if experiment is loading', () => {
|
||||
mockExperimentContext = {
|
||||
...mockExperimentContext,
|
||||
isPaintedDoorNavbarBtnVariation: false,
|
||||
experimentLoading: true,
|
||||
};
|
||||
usePaintedDoorExperimentContext.mockReturnValueOnce(mockExperimentContext);
|
||||
const wrapper = shallow(<WidgetNavbar {...props} />);
|
||||
|
||||
expect(usePaintedDoorExperimentContext).toHaveBeenCalled();
|
||||
expect(wrapper.type()).toBeNull();
|
||||
});
|
||||
});
|
||||
17
src/data/constants/course.js
Normal file
17
src/data/constants/course.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// Constants related to courses
|
||||
export const COURSE_MODES = {
|
||||
VERIFIED: 'verified',
|
||||
PROFESSIONAL: 'professional',
|
||||
NO_ID_PROFESSIONAL: 'no-id-professional',
|
||||
AUDIT: 'audit',
|
||||
HONOR: 'honor',
|
||||
EXECUTIVE_EDUCATION: 'executive-education',
|
||||
PAID_EXECUTIVE_EDUCATION: 'paid-executive-education',
|
||||
UNPAID_EXECUTIVE_EDUCATION: 'unpaid-executive-education',
|
||||
};
|
||||
|
||||
export const EXECUTIVE_EDUCATION_COURSE_MODES = [
|
||||
COURSE_MODES.EXECUTIVE_EDUCATION,
|
||||
COURSE_MODES.PAID_EXECUTIVE_EDUCATION,
|
||||
COURSE_MODES.UNPAID_EXECUTIVE_EDUCATION,
|
||||
];
|
||||
@@ -12,6 +12,7 @@ const initialState = {
|
||||
suggestedCourses: [],
|
||||
filterState: {},
|
||||
selectSessionModal: {},
|
||||
certificatePreviewModal: {},
|
||||
};
|
||||
|
||||
export const cardId = (val) => `card-${val}`;
|
||||
@@ -48,6 +49,10 @@ const app = createSlice({
|
||||
...state,
|
||||
selectSessionModal: { cardId: payload },
|
||||
}),
|
||||
updateCertificatePreviewModal: (state, { payload }) => ({
|
||||
...state,
|
||||
certificatePreviewModal: { cardId: payload },
|
||||
}),
|
||||
setPageNumber: (state, { payload }) => ({ ...state, pageNumber: payload }),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -19,9 +19,15 @@ export const showSelectSessionModal = createSelector(
|
||||
(data) => data.cardId != null,
|
||||
);
|
||||
|
||||
export const showCertificatePreviewModal = createSelector(
|
||||
[simpleSelectors.certificatePreviewModal],
|
||||
(data) => data.cardId != null,
|
||||
);
|
||||
|
||||
export default StrictDict({
|
||||
numCourses,
|
||||
hasCourses,
|
||||
hasAvailableDashboards,
|
||||
showSelectSessionModal,
|
||||
showCertificatePreviewModal,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { StrictDict } from 'utils';
|
||||
import { baseAppUrl } from 'data/services/lms/urls';
|
||||
import { EXECUTIVE_EDUCATION_COURSE_MODES } from 'data/constants/course';
|
||||
|
||||
import * as module from './courseCard';
|
||||
import * as simpleSelectors from './simpleSelectors';
|
||||
@@ -15,18 +16,14 @@ export const loadDateVal = (date) => (date ? new Date(date) : null);
|
||||
export const courseCard = StrictDict({
|
||||
certificate: mkCardSelector(
|
||||
cardSimpleSelectors.certificate,
|
||||
(certificate) => {
|
||||
const availableDate = new Date(certificate.availableDate);
|
||||
const isAvailable = availableDate <= new Date();
|
||||
return {
|
||||
availableDate,
|
||||
certPreviewUrl: baseAppUrl(certificate.certPreviewUrl),
|
||||
isDownloadable: certificate.isDownloadable,
|
||||
isEarnedButUnavailable: certificate.isEarned && !isAvailable,
|
||||
isRestricted: certificate.isRestricted,
|
||||
isEarned: certificate.isEarned,
|
||||
};
|
||||
},
|
||||
(certificate) => (certificate === null ? {} : ({
|
||||
availableDate: new Date(certificate.availableDate),
|
||||
certPreviewUrl: baseAppUrl(certificate.certPreviewUrl),
|
||||
isDownloadable: certificate.isDownloadable,
|
||||
isEarnedButUnavailable: certificate.isEarned && new Date(certificate.availableDate) > new Date(),
|
||||
isRestricted: certificate.isRestricted,
|
||||
isEarned: certificate.isEarned,
|
||||
})),
|
||||
),
|
||||
course: mkCardSelector(
|
||||
cardSimpleSelectors.course,
|
||||
@@ -101,6 +98,8 @@ export const courseCard = StrictDict({
|
||||
|
||||
isEmailEnabled: enrollment.isEmailEnabled,
|
||||
hasOptedOutOfEmail: enrollment.hasOptedOutOfEmail,
|
||||
mode: enrollment.mode,
|
||||
isExecEd2UCourse: EXECUTIVE_EDUCATION_COURSE_MODES.includes(enrollment.mode),
|
||||
};
|
||||
},
|
||||
),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { keyStore } from 'utils';
|
||||
import { baseAppUrl } from 'data/services/lms/urls';
|
||||
import { EXECUTIVE_EDUCATION_COURSE_MODES } from 'data/constants/course';
|
||||
|
||||
import simpleSelectors from './simpleSelectors';
|
||||
import * as module from './courseCard';
|
||||
@@ -79,6 +80,9 @@ describe('courseCard selectors module', () => {
|
||||
it('returns a card selector based on certificate cardSimpleSelector', () => {
|
||||
expect(simpleSelector).toEqual(cardSimpleSelectors.certificate);
|
||||
});
|
||||
it('returns {} object if null certificate received', () => {
|
||||
expect(selector(null)).toEqual({});
|
||||
});
|
||||
it('passes availableDate, converted to a date', () => {
|
||||
expect(selected.availableDate).toMatchObject(new Date(testData.availableDate));
|
||||
});
|
||||
@@ -162,6 +166,9 @@ describe('courseCard selectors module', () => {
|
||||
it('returns a card selector based on courseRun cardSimpleSelector', () => {
|
||||
expect(simpleSelector).toEqual(cardSimpleSelectors.courseRun);
|
||||
});
|
||||
it('returns {} object if null courseRun received', () => {
|
||||
expect(selector(null)).toEqual({});
|
||||
});
|
||||
it('passes [endDate, startDate], converted to dates', () => {
|
||||
expect(selected.endDate).toEqual(new Date(testData.endDate));
|
||||
expect(selected.startDate).toEqual(new Date(testData.startDate));
|
||||
@@ -222,23 +229,25 @@ describe('courseCard selectors module', () => {
|
||||
});
|
||||
});
|
||||
describe('enrollment selector', () => {
|
||||
const defaultData = {
|
||||
coursewareAccess: {
|
||||
isStaff: false,
|
||||
hasUnmetPrereqs: false,
|
||||
isTooEarly: false,
|
||||
},
|
||||
isEnrolled: 'test-is-enrolled',
|
||||
lastEnrolled: 'test-last-enrolled',
|
||||
hasStarted: 'test-has-started',
|
||||
accessExpirationDate: '3000-10-20',
|
||||
canUpgrade: 'test-can-upgrade',
|
||||
isAudit: 'test-is-audit',
|
||||
isAuditAccessExpired: 'test-is-audit-access-expired',
|
||||
isVerified: 'test-is-verified',
|
||||
isEmailEnabled: 'test-is-email-enabled',
|
||||
mode: 'default',
|
||||
};
|
||||
beforeEach(() => {
|
||||
loadSelector(courseCard.enrollment, {
|
||||
coursewareAccess: {
|
||||
isStaff: false,
|
||||
hasUnmetPrereqs: false,
|
||||
isTooEarly: false,
|
||||
},
|
||||
isEnrolled: 'test-is-enrolled',
|
||||
lastEnrolled: 'test-last-enrolled',
|
||||
hasStarted: 'test-has-started',
|
||||
accessExpirationDate: '3000-10-20',
|
||||
canUpgrade: 'test-can-upgrade',
|
||||
isAudit: 'test-is-audit',
|
||||
isAuditAccessExpired: 'test-is-audit-access-expired',
|
||||
isVerified: 'test-is-verified',
|
||||
isEmailEnabled: 'test-is-email-enabled',
|
||||
});
|
||||
loadSelector(courseCard.enrollment, defaultData);
|
||||
});
|
||||
it('returns a card selector based on enrollment cardSimpleSelector', () => {
|
||||
expect(simpleSelector).toEqual(cardSimpleSelectors.enrollment);
|
||||
@@ -274,6 +283,13 @@ describe('courseCard selectors module', () => {
|
||||
it('passes isEmailEnabled', () => {
|
||||
expect(selected.isEmailEnabled).toEqual(testData.isEmailEnabled);
|
||||
});
|
||||
it('returns isExecEd2UCourse: false if mode is not in EXECUTIVE_EDUCATION_COURSE_MODES', () => {
|
||||
expect(selected.isExecEd2UCourse).toEqual(false);
|
||||
});
|
||||
it('returns isExecEd2UCourse: true if mode is in EXECUTIVE_EDUCATION_COURSE_MODES', () => {
|
||||
loadSelector(courseCard.enrollment, { ...defaultData, mode: EXECUTIVE_EDUCATION_COURSE_MODES[0] });
|
||||
expect(selected.isExecEd2UCourse).toEqual(true);
|
||||
});
|
||||
});
|
||||
describe('entitlement selector', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -12,8 +12,9 @@ export const simpleSelectors = StrictDict({
|
||||
platformSettings: mkSimpleSelector(app => app.platformSettings),
|
||||
suggestedCourses: mkSimpleSelector(app => app.suggestedCourses),
|
||||
emailConfirmation: mkSimpleSelector(app => app.emailConfirmation),
|
||||
enterpriseDashboard: mkSimpleSelector(app => app.enterpriseDashboard),
|
||||
enterpriseDashboard: mkSimpleSelector(app => app.enterpriseDashboard || {}),
|
||||
selectSessionModal: mkSimpleSelector(app => app.selectSessionModal),
|
||||
certificatePreviewModal: mkSimpleSelector(app => app.certificatePreviewModal),
|
||||
pageNumber: mkSimpleSelector(app => app.pageNumber),
|
||||
socialShareSettings: mkSimpleSelector(app => app.socialShareSettings),
|
||||
});
|
||||
|
||||
@@ -35,6 +35,12 @@ describe('app simple selectors', () => {
|
||||
expect(preSelectors).toEqual([appSelector]);
|
||||
expect(cb(testState.app)).toEqual(testString);
|
||||
});
|
||||
test('enterpriseDashboard returns empty object if data returns null', () => {
|
||||
testState = { app: { enterpriseDashboard: null } };
|
||||
const { preSelectors, cb } = simpleSelectors.enterpriseDashboard;
|
||||
expect(preSelectors).toEqual([appSelector]);
|
||||
expect(cb(testState.app)).toEqual({});
|
||||
});
|
||||
describe('cardSimpleSelectors', () => {
|
||||
keys = keyStore(cardSimpleSelectors);
|
||||
test.each([
|
||||
|
||||
@@ -13,6 +13,8 @@ export const useEmailConfirmationData = () => useSelector(selectors.emailConfirm
|
||||
export const useEnterpriseDashboardData = () => useSelector(selectors.enterpriseDashboard);
|
||||
export const usePlatformSettingsData = () => useSelector(selectors.platformSettings);
|
||||
export const useSelectSessionModalData = () => useSelector(selectors.selectSessionModal);
|
||||
export const useCertificatePreviewData = () => useSelector(selectors.certificatePreviewModal);
|
||||
|
||||
export const useSocialShareSettings = () => useSelector(selectors.socialShareSettings);
|
||||
|
||||
/** global-level meta-selectors **/
|
||||
@@ -22,6 +24,7 @@ export const useCurrentCourseList = (opts) => useSelector(
|
||||
state => selectors.currentList(state, opts),
|
||||
);
|
||||
export const useShowSelectSessionModal = () => useSelector(selectors.showSelectSessionModal);
|
||||
export const useShowCertificatePreviewModal = () => useSelector(selectors.showCertificatePreviewModal);
|
||||
|
||||
// eslint-disable-next-line
|
||||
export const useCourseCardData = (selector) => (cardId) => useSelector(
|
||||
@@ -55,12 +58,23 @@ export const useCardSocialSettingsData = (cardId) => {
|
||||
return { facebook: loadSettings(facebook), twitter: loadSettings(twitter) };
|
||||
};
|
||||
|
||||
export const useCardExecEdTrackingParam = (cardId) => {
|
||||
const { isExecEd2UCourse } = module.useCardEnrollmentData(cardId);
|
||||
const { authOrgId } = module.useEnterpriseDashboardData(cardId);
|
||||
return isExecEd2UCourse ? `?org_id=${authOrgId}` : '';
|
||||
};
|
||||
|
||||
/** Events **/
|
||||
export const useUpdateSelectSessionModalCallback = (cardId) => {
|
||||
const dispatch = useDispatch();
|
||||
return () => dispatch(actions.updateSelectSessionModal(cardId));
|
||||
};
|
||||
|
||||
export const useUpdateCertificatePreviewModalCallback = (cardId) => {
|
||||
const dispatch = useDispatch();
|
||||
return () => dispatch(actions.updateCertificatePreviewModal(cardId));
|
||||
};
|
||||
|
||||
export const useTrackCourseEvent = (tracker, cardId, ...args) => {
|
||||
const { courseId } = module.useCardCourseRunData(cardId);
|
||||
return (e) => tracker(courseId, ...args)(e);
|
||||
|
||||
@@ -17,7 +17,7 @@ import * as module from './api';
|
||||
* GET Actions
|
||||
*********************************************************************************/
|
||||
export const initializeList = ({ user } = {}) => get(
|
||||
stringifyUrl(urls.init, { [apiKeys.user]: user }),
|
||||
stringifyUrl(urls.getInitApiUrl(), { [apiKeys.user]: user }),
|
||||
);
|
||||
|
||||
export const updateEntitlementEnrollment = ({ uuid, courseId }) => post(
|
||||
|
||||
@@ -43,7 +43,7 @@ describe('lms api methods', () => {
|
||||
[apiKeys.user]: testUser,
|
||||
};
|
||||
expect(api.initializeList(userArg)).toEqual(
|
||||
utils.get(utils.stringifyUrl(urls.init, userArg)),
|
||||
utils.get(utils.stringifyUrl(urls.getInitApiUrl(), userArg)),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -761,10 +761,13 @@ export const compileCourseRunData = ({ courseName, ...data }, index) => {
|
||||
credit: {},
|
||||
...data,
|
||||
certificate: genCertificateData(data.certificate),
|
||||
enrollment: genEnrollmentData({ lastEnrolled, ...data.enrollment }),
|
||||
enrollment: genEnrollmentData({
|
||||
lastEnrolled,
|
||||
...getOption(emailOptions, index),
|
||||
...data.enrollment,
|
||||
}),
|
||||
courseRun: genCourseRunData({
|
||||
...data.courseRun,
|
||||
...getOption(emailOptions, index),
|
||||
courseId,
|
||||
}),
|
||||
course: {
|
||||
|
||||
@@ -1,40 +1,41 @@
|
||||
import { StrictDict } from 'utils';
|
||||
import { configuration } from 'config';
|
||||
|
||||
const baseUrl = `${configuration.LMS_BASE_URL}`;
|
||||
export const ecommerceUrl = `${configuration.ECOMMERCE_BASE_URL}`;
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
|
||||
export const api = `${baseUrl}/api`;
|
||||
export const getEcommerceUrl = () => getConfig().ECOMMERCE_BASE_URL;
|
||||
|
||||
// const init = `${api}learner_home/mock/init`; // mock endpoint for testing
|
||||
const init = `${api}/learner_home/init`;
|
||||
const getBaseUrl = () => getConfig().LMS_BASE_URL;
|
||||
|
||||
const event = `${baseUrl}/event`;
|
||||
const courseUnenroll = `${baseUrl}/change_enrollment`;
|
||||
const updateEmailSettings = `${api}/change_email_settings`;
|
||||
const entitlementEnrollment = (uuid) => `${api}/entitlements/v1/entitlements/${uuid}/enrollments`;
|
||||
export const getApiUrl = () => (`${getConfig().LMS_BASE_URL}/api`);
|
||||
|
||||
const getInitApiUrl = () => (`${getApiUrl()}/learner_home/init`);
|
||||
|
||||
const event = `${getBaseUrl()}/event`;
|
||||
const courseUnenroll = `${getBaseUrl()}/change_enrollment`;
|
||||
const updateEmailSettings = `${getApiUrl()}/change_email_settings`;
|
||||
const entitlementEnrollment = (uuid) => `${getApiUrl()}/entitlements/v1/entitlements/${uuid}/enrollments`;
|
||||
|
||||
// if url is null or absolute, return it as is
|
||||
const updateUrl = (base, url) => ((url == null || url.startsWith('http://') || url.startsWith('https://')) ? url : `${base}${url}`);
|
||||
export const updateUrl = (base, url) => ((url == null || url.startsWith('http://') || url.startsWith('https://')) ? url : `${base}${url}`);
|
||||
|
||||
export const baseAppUrl = (url) => updateUrl(baseUrl, url);
|
||||
export const learningMfeUrl = (url) => updateUrl(configuration.LEARNING_BASE_URL, url);
|
||||
export const baseAppUrl = (url) => updateUrl(getBaseUrl(), url);
|
||||
export const learningMfeUrl = (url) => updateUrl(getConfig().LEARNING_BASE_URL, url);
|
||||
|
||||
// static view url
|
||||
const programsUrl = baseAppUrl('/dashboard/programs');
|
||||
|
||||
export const creditPurchaseUrl = (courseId) => `${ecommerceUrl}/credit/checkout/${courseId}/`;
|
||||
export const creditRequestUrl = (providerId) => `${api}/credit/v1/providers/${providerId}/request/`;
|
||||
export const creditPurchaseUrl = (courseId) => `${getEcommerceUrl()}/credit/checkout/${courseId}/`;
|
||||
export const creditRequestUrl = (providerId) => `${getApiUrl()}/credit/v1/providers/${providerId}/request/`;
|
||||
|
||||
export default StrictDict({
|
||||
api,
|
||||
getApiUrl,
|
||||
baseAppUrl,
|
||||
courseUnenroll,
|
||||
creditPurchaseUrl,
|
||||
creditRequestUrl,
|
||||
entitlementEnrollment,
|
||||
event,
|
||||
init,
|
||||
getInitApiUrl,
|
||||
learningMfeUrl,
|
||||
programsUrl,
|
||||
updateEmailSettings,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { configuration } from 'config';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import * as urls from './urls';
|
||||
|
||||
describe('urls', () => {
|
||||
@@ -10,7 +10,7 @@ describe('urls', () => {
|
||||
it('returns the url if it is relative', () => {
|
||||
const url = '/edx.org';
|
||||
expect(urls.baseAppUrl(url)).toEqual(
|
||||
`${configuration.LMS_BASE_URL}${url}`,
|
||||
`${getConfig().LMS_BASE_URL}${url}`,
|
||||
);
|
||||
});
|
||||
it('return null if url is null', () => {
|
||||
@@ -25,7 +25,7 @@ describe('urls', () => {
|
||||
it('returns the url if it is relative', () => {
|
||||
const url = '/edx.org';
|
||||
expect(urls.learningMfeUrl(url)).toEqual(
|
||||
`${configuration.LEARNING_BASE_URL}${url}`,
|
||||
`${getConfig().LEARNING_BASE_URL}${url}`,
|
||||
);
|
||||
});
|
||||
it('return null if url is null', () => {
|
||||
@@ -36,7 +36,6 @@ describe('urls', () => {
|
||||
it('builds from ecommerce url and loads courseId', () => {
|
||||
const courseId = 'test-course-id';
|
||||
const url = urls.creditPurchaseUrl(courseId);
|
||||
expect(url.startsWith(urls.ecommerceUrl)).toEqual(true);
|
||||
expect(url).toEqual(expect.stringContaining(courseId));
|
||||
});
|
||||
});
|
||||
@@ -44,7 +43,7 @@ describe('urls', () => {
|
||||
it('builds from api url and loads providerId', () => {
|
||||
const providerId = 'test-provider-id';
|
||||
const url = urls.creditRequestUrl(providerId);
|
||||
expect(url.startsWith(urls.api)).toEqual(true);
|
||||
expect(url.startsWith(urls.getApiUrl())).toEqual(true);
|
||||
expect(url).toEqual(expect.stringContaining(providerId));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { messages as footerMessages } from '@edx/frontend-component-footer';
|
||||
import { messages as paragonMessages } from '@edx/paragon';
|
||||
|
||||
import arMessages from './messages/ar.json';
|
||||
// no need to import en messages-- they are in the defaultMessage field
|
||||
@@ -20,4 +21,5 @@ const appMessages = {
|
||||
export default [
|
||||
footerMessages,
|
||||
appMessages,
|
||||
paragonMessages,
|
||||
];
|
||||
|
||||
@@ -14,25 +14,12 @@
|
||||
"leanerDashboard.confirmEmailModalHeader": "قم بتأكيد بريدك الإلكتروني",
|
||||
"leanerDashboard.confirmEmailModalBody": "لقد أرسلنا إليك بريدًا إلكترونيًا للتحقق من حسابك. يرجى التحقق من بريدك الوارد والنقر على الزر الأحمر الكبير للتأكيد ومتابعة التعلم.",
|
||||
"leanerDashboard.confirmEmailImageAlt": "ايقونة زر تأكيد البريد الإلكتروني",
|
||||
"leanerDashboard.menu.dashboard.label": "لوحة التحكم",
|
||||
"leanerDashboard.help.label": "المساعدة",
|
||||
"leanerDashboard.menu.profile.label": "الملف الشخصي",
|
||||
"leanerDashboard.menu.viewPrograms.label": "عرض البرامج",
|
||||
"leanerDashboard.menu.account.label": "الحساب",
|
||||
"leanerDashboard.menu.orderHistory.label": "المشتريات السابقة",
|
||||
"leanerDashboard.menu.signOut.label": "تسجيل الخروج",
|
||||
"greeting.morning": "صباح الخير!",
|
||||
"greeting.afternoon": "مساء الخير!",
|
||||
"greeting.evening": "مساء الخير!",
|
||||
"leanerDashboard.switchToProgram": "انتقل إلى لائحة البرامج",
|
||||
"leanerDashboard.exploreCourses": "استكشف المساقات",
|
||||
"leanerDashboard.courseSearchAlt": "البحث عن مساق",
|
||||
"learnerVariantDashboard.menu.dashboard.label": "لوحة التحكم",
|
||||
"learnerVariantDashboard.help.label": "المساعدة",
|
||||
"learnerVariantDashboard.menu.profile.label": "الملف الشخصي",
|
||||
"learnerVariantDashboard.menu.viewPrograms.label": "عرض البرامج",
|
||||
"learnerVariantDashboard.menu.account.label": "الحساب",
|
||||
"learnerVariantDashboard.menu.orderHistory.label": "المشتريات السابقة",
|
||||
"learnerVariantDashboard.menu.orderHistory.label": "Order History",
|
||||
"learnerVariantDashboard.menu.signOut.label": "تسجيل الخروج",
|
||||
"learnerVariantDashboard.course": "المساقات",
|
||||
"learnerVariantDashboard.program": "البرامج",
|
||||
@@ -40,6 +27,8 @@
|
||||
"learnerVariantDashboard.logoAltText": ". لوحة القيادة",
|
||||
"learnerVariantDashboard.collapseMenuOpenAltText": "القائمة",
|
||||
"learnerVariantDashboard.collapseMenuClosedAltText": "إغلاق",
|
||||
"leanerDashboard.menu.career.label": "Career",
|
||||
"header.menu.new.label": "New",
|
||||
"MasqueradeBar.ViewAs": "عرض كـ :",
|
||||
"MasqueradeBar.ViewingAs": "يتم عرض كـ:",
|
||||
"MasqueradeBar.SubmitButton": "تأكيد",
|
||||
@@ -48,6 +37,20 @@
|
||||
"MasqueradeBar.UnknownError": "حدث خطأ غير معروف",
|
||||
"WidgetSidebar.lookingForChallengePrompt": "هل تبحث عن تحد جديد؟",
|
||||
"WidgetSidebar.findCoursesButton": "ابحث عن مساق {arrow}",
|
||||
"ProductRecommendations.recommendationsHeading": "You might also like",
|
||||
"ProductRecommendations.executiveEducationHeading": "Executive Education",
|
||||
"ProductRecommendations.executiveEducationDescription": "Short Courses to develop leadership skills",
|
||||
"ProductRecommendations.bootcampHeading": "Boot Camp",
|
||||
"ProductRecommendations.bootcampDescription": "Intensive, hands-on, project based training",
|
||||
"ProductRecommendations.courseHeading": "Courses",
|
||||
"ProductRecommendations.courseDescription": "Find new interests and advance your career",
|
||||
"RecommendationsPanel.recommendationsFeatureText": "Personalized recommendations feature is not yet available. We are working hard on bringing it to your learner home in the near future.",
|
||||
"RecommendationsPanel.recommendationsAlertText": "Would you like to be alerted when it becomes available?",
|
||||
"RecommendationsPanel.recommendationsModalHeading": "Thank you for your interest!",
|
||||
"RecommendationsPanel.modalSkipButton": "Skip for now",
|
||||
"RecommendationsPanel.modalCountMeButton": "Count me in!",
|
||||
"learnerVariantDashboard.recommendedForYou": "Recommended For You",
|
||||
"RecommendationsPanel.seeAllRecommendationsButton": "See All Recommendations",
|
||||
"RecommendationsPanel.recommendationsHeading": "توصيات خاصة لك",
|
||||
"RecommendationsPanel.popularCoursesHeading": "المساقات الشائعة",
|
||||
"RecommendationsPanel.exploreCoursesButton": "استكشف المساقات"
|
||||
|
||||
@@ -7,26 +7,13 @@
|
||||
"leanerDashboard.enterpriseDialogHeader": "Tienes acceso al panel {label}",
|
||||
"leanerDashboard.enterpriseDialogBody": "Para acceder a los cursos disponibles para usted a través de {label}, visite el panel {label} ahora.",
|
||||
"leanerDashboard.enterpriseDialogDismissButton": "Despedir",
|
||||
"leanerDashboard.enterpriseDialogConfirmButton": "Go to dashboard",
|
||||
"leanerDashboard.enterpriseDialogConfirmButton": "Ir al panel principal",
|
||||
"leanerDashboard.confirmEmailBanner": "Confirmar ahora",
|
||||
"leanerDashboard.confirmEmailTextReminderBanner": "¡Recuerda confirmar tu correo electrónico para que puedas seguir aprendiendo en edX! {confirmNowButton}.",
|
||||
"leanerDashboard.verifiedConfirmEmailButton": "He confirmado mi correo electrónico.",
|
||||
"leanerDashboard.confirmEmailModalHeader": "confirme su email",
|
||||
"leanerDashboard.confirmEmailModalBody": "Te hemos enviado un correo electrónico para verificar tu cuenta. Revise su bandeja de entrada y haga clic en el botón rojo grande para confirmar y seguir aprendiendo.",
|
||||
"leanerDashboard.confirmEmailImageAlt": "confirmar fondo de correo electrónico",
|
||||
"leanerDashboard.menu.dashboard.label": "Panel de Control",
|
||||
"leanerDashboard.help.label": "Ayuda",
|
||||
"leanerDashboard.menu.profile.label": "Perfil",
|
||||
"leanerDashboard.menu.viewPrograms.label": "Ver programas",
|
||||
"leanerDashboard.menu.account.label": "Cuenta",
|
||||
"leanerDashboard.menu.orderHistory.label": "Historial de órdenes",
|
||||
"leanerDashboard.menu.signOut.label": "Cerrar sesión",
|
||||
"greeting.morning": "¡Buen día!",
|
||||
"greeting.afternoon": "¡Buenas tardes!",
|
||||
"greeting.evening": "¡Buenas noches!",
|
||||
"leanerDashboard.switchToProgram": "Cambiar a Programas",
|
||||
"leanerDashboard.exploreCourses": "Explorar cursos",
|
||||
"leanerDashboard.courseSearchAlt": "Búsqueda de cursos",
|
||||
"learnerVariantDashboard.menu.dashboard.label": "Panel de Control",
|
||||
"learnerVariantDashboard.help.label": "Ayuda",
|
||||
"learnerVariantDashboard.menu.profile.label": "Perfil",
|
||||
@@ -40,6 +27,8 @@
|
||||
"learnerVariantDashboard.logoAltText": "Tablero de edX, Inc.",
|
||||
"learnerVariantDashboard.collapseMenuOpenAltText": "Menú",
|
||||
"learnerVariantDashboard.collapseMenuClosedAltText": "Cerrar",
|
||||
"leanerDashboard.menu.career.label": "Carrera",
|
||||
"header.menu.new.label": "Nuevo",
|
||||
"MasqueradeBar.ViewAs": "Ver como:",
|
||||
"MasqueradeBar.ViewingAs": "Viendo como:",
|
||||
"MasqueradeBar.SubmitButton": "Enviar",
|
||||
@@ -48,6 +37,20 @@
|
||||
"MasqueradeBar.UnknownError": "Un error desconocido ocurrió",
|
||||
"WidgetSidebar.lookingForChallengePrompt": "¿Buscando un nuevo reto?",
|
||||
"WidgetSidebar.findCoursesButton": "Encuentra un curso {arrow}",
|
||||
"ProductRecommendations.recommendationsHeading": "También podría ser de interés",
|
||||
"ProductRecommendations.executiveEducationHeading": "Formación Ejecutiva",
|
||||
"ProductRecommendations.executiveEducationDescription": "Cursos cortos para desarrollar habilidades de liderazgo",
|
||||
"ProductRecommendations.bootcampHeading": "Capacitación intensiva de corta duración",
|
||||
"ProductRecommendations.bootcampDescription": "Capacitación intensiva, práctica y basada en proyectos",
|
||||
"ProductRecommendations.courseHeading": "Cursos",
|
||||
"ProductRecommendations.courseDescription": "Encontrar nuevos intereses y avanzar en la carrera",
|
||||
"RecommendationsPanel.recommendationsFeatureText": "La función de recomendaciones personalizadas aún no está disponible. Estamos trabajando arduamente para llevarlo a casa de su alumno en un futuro próximo.",
|
||||
"RecommendationsPanel.recommendationsAlertText": "¿Le gustaría recibir una alerta cuando esté disponible?",
|
||||
"RecommendationsPanel.recommendationsModalHeading": "¡Gracias por su interés!",
|
||||
"RecommendationsPanel.modalSkipButton": "Saltar por ahora ",
|
||||
"RecommendationsPanel.modalCountMeButton": "¡Cuente conmigo!",
|
||||
"learnerVariantDashboard.recommendedForYou": "Recomendado para usted",
|
||||
"RecommendationsPanel.seeAllRecommendationsButton": "Ver todas las recomendaciones",
|
||||
"RecommendationsPanel.recommendationsHeading": "Recomendaciones para ti",
|
||||
"RecommendationsPanel.popularCoursesHeading": "Cursos populares",
|
||||
"RecommendationsPanel.exploreCoursesButton": "Explorar cursos"
|
||||
|
||||
@@ -14,25 +14,12 @@
|
||||
"leanerDashboard.confirmEmailModalHeader": "Confirmer votre courriel",
|
||||
"leanerDashboard.confirmEmailModalBody": "Nous vous avons envoyé un courriel pour vérifier votre compte. Veuillez vérifier votre boîte de réception et cliquer sur le gros bouton rouge pour confirmer et continuer à apprendre.",
|
||||
"leanerDashboard.confirmEmailImageAlt": "confirmer l'arrière-plan du courriel",
|
||||
"leanerDashboard.menu.dashboard.label": "Tableau de bord",
|
||||
"leanerDashboard.help.label": "Aide",
|
||||
"leanerDashboard.menu.profile.label": "Profil",
|
||||
"leanerDashboard.menu.viewPrograms.label": "Voir les programmes",
|
||||
"leanerDashboard.menu.account.label": "Compte",
|
||||
"leanerDashboard.menu.orderHistory.label": "Historique des commandes",
|
||||
"leanerDashboard.menu.signOut.label": "Se déconnecter",
|
||||
"greeting.morning": "Bonjour!",
|
||||
"greeting.afternoon": "Bon après-midi!",
|
||||
"greeting.evening": "Bonne soirée!",
|
||||
"leanerDashboard.switchToProgram": "Passer aux programmes",
|
||||
"leanerDashboard.exploreCourses": "Explorer les cours",
|
||||
"leanerDashboard.courseSearchAlt": "Recherche de cours",
|
||||
"learnerVariantDashboard.menu.dashboard.label": "Tableau de bord",
|
||||
"learnerVariantDashboard.help.label": "Aide",
|
||||
"learnerVariantDashboard.menu.profile.label": "Profil",
|
||||
"learnerVariantDashboard.menu.viewPrograms.label": "Voir les programmes",
|
||||
"learnerVariantDashboard.menu.account.label": "Compte",
|
||||
"learnerVariantDashboard.menu.orderHistory.label": "Historique des commandes",
|
||||
"learnerVariantDashboard.menu.orderHistory.label": "Order History",
|
||||
"learnerVariantDashboard.menu.signOut.label": "Se déconnecter",
|
||||
"learnerVariantDashboard.course": "Cours",
|
||||
"learnerVariantDashboard.program": "Programmes",
|
||||
@@ -40,6 +27,8 @@
|
||||
"learnerVariantDashboard.logoAltText": "Tableau de bord edX, Inc.",
|
||||
"learnerVariantDashboard.collapseMenuOpenAltText": "Menu",
|
||||
"learnerVariantDashboard.collapseMenuClosedAltText": "Fermer",
|
||||
"leanerDashboard.menu.career.label": "Career",
|
||||
"header.menu.new.label": "New",
|
||||
"MasqueradeBar.ViewAs": "Vue de :",
|
||||
"MasqueradeBar.ViewingAs": "Voir comme : ",
|
||||
"MasqueradeBar.SubmitButton": "Envoyez",
|
||||
@@ -48,6 +37,20 @@
|
||||
"MasqueradeBar.UnknownError": "Une erreur d'origine inconnue s'est produite.",
|
||||
"WidgetSidebar.lookingForChallengePrompt": "A la recherche d'un nouveau défi?",
|
||||
"WidgetSidebar.findCoursesButton": "Trouver un cours {arrow}",
|
||||
"ProductRecommendations.recommendationsHeading": "You might also like",
|
||||
"ProductRecommendations.executiveEducationHeading": "Executive Education",
|
||||
"ProductRecommendations.executiveEducationDescription": "Short Courses to develop leadership skills",
|
||||
"ProductRecommendations.bootcampHeading": "Boot Camp",
|
||||
"ProductRecommendations.bootcampDescription": "Intensive, hands-on, project based training",
|
||||
"ProductRecommendations.courseHeading": "Courses",
|
||||
"ProductRecommendations.courseDescription": "Find new interests and advance your career",
|
||||
"RecommendationsPanel.recommendationsFeatureText": "Personalized recommendations feature is not yet available. We are working hard on bringing it to your learner home in the near future.",
|
||||
"RecommendationsPanel.recommendationsAlertText": "Would you like to be alerted when it becomes available?",
|
||||
"RecommendationsPanel.recommendationsModalHeading": "Thank you for your interest!",
|
||||
"RecommendationsPanel.modalSkipButton": "Skip for now",
|
||||
"RecommendationsPanel.modalCountMeButton": "Count me in!",
|
||||
"learnerVariantDashboard.recommendedForYou": "Recommended For You",
|
||||
"RecommendationsPanel.seeAllRecommendationsButton": "See All Recommendations",
|
||||
"RecommendationsPanel.recommendationsHeading": "Des recommandations pour vous",
|
||||
"RecommendationsPanel.popularCoursesHeading": "Cours populaires",
|
||||
"RecommendationsPanel.exploreCoursesButton": "Explorer les cours"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"dashboard.mycourses": "Mes cours",
|
||||
"Dashboard.NoCoursesView.lookingForChallengePrompt": "A la recherche d'un nouveau défi ?",
|
||||
"Dashboard.NoCoursesView.lookingForChallengePrompt": "A la recherche d'un nouveau défi?",
|
||||
"Dashboard.NoCoursesView.exploreCoursesPrompt": "Explorez nos cours pour les ajouter à votre tableau de bord.",
|
||||
"Dashboard.NoCoursesView.exploreCoursesButton": "Explorer les cours",
|
||||
"Dashboard.NoCoursesView.bannerAlt": "Aucune bannière de cours",
|
||||
@@ -9,24 +9,11 @@
|
||||
"leanerDashboard.enterpriseDialogDismissButton": "Rejeter",
|
||||
"leanerDashboard.enterpriseDialogConfirmButton": "Aller au tableau de bord",
|
||||
"leanerDashboard.confirmEmailBanner": "Confirmer maintenant",
|
||||
"leanerDashboard.confirmEmailTextReminderBanner": "N'oubliez pas de confirmer votre courriel afin de pouvoir continuer à apprendre sur edX ! {confirmNowButton}.",
|
||||
"leanerDashboard.confirmEmailTextReminderBanner": "N'oubliez pas de confirmer votre courriel afin de pouvoir continuer à apprendre sur EDUlib! {confirmNowButton}.",
|
||||
"leanerDashboard.verifiedConfirmEmailButton": "J'ai confirmé mon courriel",
|
||||
"leanerDashboard.confirmEmailModalHeader": "Confirmer votre courriel",
|
||||
"leanerDashboard.confirmEmailModalBody": "Nous vous avons envoyé un courriel pour vérifier votre compte. Veuillez vérifier votre boîte de réception et cliquer sur le gros bouton rouge pour confirmer et continuer à apprendre.",
|
||||
"leanerDashboard.confirmEmailImageAlt": "confirmer l'arrière-plan du courriel",
|
||||
"leanerDashboard.menu.dashboard.label": "Tableau de bord",
|
||||
"leanerDashboard.help.label": "Aide",
|
||||
"leanerDashboard.menu.profile.label": "Profil",
|
||||
"leanerDashboard.menu.viewPrograms.label": "Voir les programmes",
|
||||
"leanerDashboard.menu.account.label": "Compte",
|
||||
"leanerDashboard.menu.orderHistory.label": "Historique des commandes",
|
||||
"leanerDashboard.menu.signOut.label": "Se déconnecter",
|
||||
"greeting.morning": "Bonjour!",
|
||||
"greeting.afternoon": "Bon après-midi!",
|
||||
"greeting.evening": "Bonne soirée!",
|
||||
"leanerDashboard.switchToProgram": "Passer aux programmes",
|
||||
"leanerDashboard.exploreCourses": "Explorer les cours",
|
||||
"leanerDashboard.courseSearchAlt": "Recherche de cours",
|
||||
"learnerVariantDashboard.menu.dashboard.label": "Tableau de bord",
|
||||
"learnerVariantDashboard.help.label": "Aide",
|
||||
"learnerVariantDashboard.menu.profile.label": "Profil",
|
||||
@@ -37,17 +24,33 @@
|
||||
"learnerVariantDashboard.course": "Cours",
|
||||
"learnerVariantDashboard.program": "Programmes",
|
||||
"learnerVariantDashboard.discoverNew": "Découvrir les nouveautés",
|
||||
"learnerVariantDashboard.logoAltText": "Tableau de bord edX, Inc.",
|
||||
"learnerVariantDashboard.logoAltText": "Tableau de bord EDUlib, Inc.",
|
||||
"learnerVariantDashboard.collapseMenuOpenAltText": "Menu",
|
||||
"learnerVariantDashboard.collapseMenuClosedAltText": "Fermer",
|
||||
"leanerDashboard.menu.career.label": "Carrière",
|
||||
"header.menu.new.label": "Nouveau",
|
||||
"MasqueradeBar.ViewAs": "Consulter comme :",
|
||||
"MasqueradeBar.ViewingAs": "Affichage en tant que :",
|
||||
"MasqueradeBar.SubmitButton": "Soumettre",
|
||||
"MasqueradeBar.StudentNameInput": "Nom d'utilisateur ou courriel",
|
||||
"MasqueradeBar.NoStudentFound": "Aucun étudiant avec ce nom d'utilisateur ou cette adresse courriel n'a pu être trouvé",
|
||||
"MasqueradeBar.UnknownError": "Une erreur inconnue est survenue",
|
||||
"WidgetSidebar.lookingForChallengePrompt": "A la recherche d'un nouveau défi ?",
|
||||
"WidgetSidebar.lookingForChallengePrompt": "A la recherche d'un nouveau défi?",
|
||||
"WidgetSidebar.findCoursesButton": "Trouver un cours {arrow}",
|
||||
"ProductRecommendations.recommendationsHeading": "Vous pourriez aussi aimer",
|
||||
"ProductRecommendations.executiveEducationHeading": "Formation des cadres",
|
||||
"ProductRecommendations.executiveEducationDescription": "Cours abrégés pour développer les compétences en leadership",
|
||||
"ProductRecommendations.bootcampHeading": "Camp d'entraînement",
|
||||
"ProductRecommendations.bootcampDescription": "Formation intensive, pratique et basée sur des projets",
|
||||
"ProductRecommendations.courseHeading": "Cours",
|
||||
"ProductRecommendations.courseDescription": "Trouvez de nouveaux intérêts et faites progresser votre carrière",
|
||||
"RecommendationsPanel.recommendationsFeatureText": "La fonctionnalité de recommandations personnalisées n'est pas encore disponible. Nous travaillons fort pour le proposer à votre apprenant dans un avenir rapproché.",
|
||||
"RecommendationsPanel.recommendationsAlertText": "Souhaitez-vous être alerté dès qu'il sera disponible?",
|
||||
"RecommendationsPanel.recommendationsModalHeading": "Merci pour votre intérêt!",
|
||||
"RecommendationsPanel.modalSkipButton": "Ignorer pour l'instant",
|
||||
"RecommendationsPanel.modalCountMeButton": "Comptez sur moi!",
|
||||
"learnerVariantDashboard.recommendedForYou": "Recommandé pour vous",
|
||||
"RecommendationsPanel.seeAllRecommendationsButton": "Voir toutes les recommandations",
|
||||
"RecommendationsPanel.recommendationsHeading": "Des recommandations pour vous",
|
||||
"RecommendationsPanel.popularCoursesHeading": "Cours populaires",
|
||||
"RecommendationsPanel.exploreCoursesButton": "Explorer les cours"
|
||||
|
||||
@@ -14,25 +14,12 @@
|
||||
"leanerDashboard.confirmEmailModalHeader": "Confirm your email",
|
||||
"leanerDashboard.confirmEmailModalBody": "We've sent you an email to verify your acccount. Please check your inbox and click on the big red button to confirm and keep learning.",
|
||||
"leanerDashboard.confirmEmailImageAlt": "confirm email background",
|
||||
"leanerDashboard.menu.dashboard.label": "Painel de controle",
|
||||
"leanerDashboard.help.label": "Ajuda",
|
||||
"leanerDashboard.menu.profile.label": "Perfil",
|
||||
"leanerDashboard.menu.viewPrograms.label": "View Programs",
|
||||
"leanerDashboard.menu.account.label": "Conta",
|
||||
"leanerDashboard.menu.orderHistory.label": "Histórico de pedidos",
|
||||
"leanerDashboard.menu.signOut.label": "Sair",
|
||||
"greeting.morning": "Good Morning!",
|
||||
"greeting.afternoon": "Good Afternoon!",
|
||||
"greeting.evening": "Good Evening!",
|
||||
"leanerDashboard.switchToProgram": "Switch to Programs",
|
||||
"leanerDashboard.exploreCourses": "Explorar cursos",
|
||||
"leanerDashboard.courseSearchAlt": "Course search",
|
||||
"learnerVariantDashboard.menu.dashboard.label": "Painel de controle",
|
||||
"learnerVariantDashboard.help.label": "Ajuda",
|
||||
"learnerVariantDashboard.menu.profile.label": "Perfil",
|
||||
"learnerVariantDashboard.menu.viewPrograms.label": "View Programs",
|
||||
"learnerVariantDashboard.menu.account.label": "Conta",
|
||||
"learnerVariantDashboard.menu.orderHistory.label": "Histórico de pedidos",
|
||||
"learnerVariantDashboard.menu.orderHistory.label": "Order History",
|
||||
"learnerVariantDashboard.menu.signOut.label": "Sair",
|
||||
"learnerVariantDashboard.course": "Cursos",
|
||||
"learnerVariantDashboard.program": "Programas",
|
||||
@@ -40,6 +27,8 @@
|
||||
"learnerVariantDashboard.logoAltText": "edX, Inc. Dashboard",
|
||||
"learnerVariantDashboard.collapseMenuOpenAltText": "Menu",
|
||||
"learnerVariantDashboard.collapseMenuClosedAltText": "Fechar",
|
||||
"leanerDashboard.menu.career.label": "Career",
|
||||
"header.menu.new.label": "New",
|
||||
"MasqueradeBar.ViewAs": "Ver como: ",
|
||||
"MasqueradeBar.ViewingAs": "Viewing as: ",
|
||||
"MasqueradeBar.SubmitButton": "Enviar",
|
||||
@@ -48,6 +37,20 @@
|
||||
"MasqueradeBar.UnknownError": "An unknown error occurred",
|
||||
"WidgetSidebar.lookingForChallengePrompt": "Looking for a new challenge?",
|
||||
"WidgetSidebar.findCoursesButton": "Find a course {arrow}",
|
||||
"ProductRecommendations.recommendationsHeading": "You might also like",
|
||||
"ProductRecommendations.executiveEducationHeading": "Executive Education",
|
||||
"ProductRecommendations.executiveEducationDescription": "Short Courses to develop leadership skills",
|
||||
"ProductRecommendations.bootcampHeading": "Boot Camp",
|
||||
"ProductRecommendations.bootcampDescription": "Intensive, hands-on, project based training",
|
||||
"ProductRecommendations.courseHeading": "Courses",
|
||||
"ProductRecommendations.courseDescription": "Find new interests and advance your career",
|
||||
"RecommendationsPanel.recommendationsFeatureText": "Personalized recommendations feature is not yet available. We are working hard on bringing it to your learner home in the near future.",
|
||||
"RecommendationsPanel.recommendationsAlertText": "Would you like to be alerted when it becomes available?",
|
||||
"RecommendationsPanel.recommendationsModalHeading": "Thank you for your interest!",
|
||||
"RecommendationsPanel.modalSkipButton": "Skip for now",
|
||||
"RecommendationsPanel.modalCountMeButton": "Count me in!",
|
||||
"learnerVariantDashboard.recommendedForYou": "Recommended For You",
|
||||
"RecommendationsPanel.seeAllRecommendationsButton": "See All Recommendations",
|
||||
"RecommendationsPanel.recommendationsHeading": "Recommendations for you",
|
||||
"RecommendationsPanel.popularCoursesHeading": "Popular courses",
|
||||
"RecommendationsPanel.exploreCoursesButton": "Explorar cursos"
|
||||
|
||||
@@ -14,19 +14,6 @@
|
||||
"leanerDashboard.confirmEmailModalHeader": "Confirm your email",
|
||||
"leanerDashboard.confirmEmailModalBody": "We've sent you an email to verify your acccount. Please check your inbox and click on the big red button to confirm and keep learning.",
|
||||
"leanerDashboard.confirmEmailImageAlt": "confirm email background",
|
||||
"leanerDashboard.menu.dashboard.label": "Dashboard",
|
||||
"leanerDashboard.help.label": "Help",
|
||||
"leanerDashboard.menu.profile.label": "Profile",
|
||||
"leanerDashboard.menu.viewPrograms.label": "View Programs",
|
||||
"leanerDashboard.menu.account.label": "Account",
|
||||
"leanerDashboard.menu.orderHistory.label": "Order History",
|
||||
"leanerDashboard.menu.signOut.label": "Sign Out",
|
||||
"greeting.morning": "Good Morning!",
|
||||
"greeting.afternoon": "Good Afternoon!",
|
||||
"greeting.evening": "Good Evening!",
|
||||
"leanerDashboard.switchToProgram": "Switch to Programs",
|
||||
"leanerDashboard.exploreCourses": "Explore courses",
|
||||
"leanerDashboard.courseSearchAlt": "Course search",
|
||||
"learnerVariantDashboard.menu.dashboard.label": "Dashboard",
|
||||
"learnerVariantDashboard.help.label": "Help",
|
||||
"learnerVariantDashboard.menu.profile.label": "Profile",
|
||||
@@ -40,6 +27,8 @@
|
||||
"learnerVariantDashboard.logoAltText": "edX, Inc. Dashboard",
|
||||
"learnerVariantDashboard.collapseMenuOpenAltText": "Menu",
|
||||
"learnerVariantDashboard.collapseMenuClosedAltText": "Close",
|
||||
"leanerDashboard.menu.career.label": "Career",
|
||||
"header.menu.new.label": "New",
|
||||
"MasqueradeBar.ViewAs": "View as: ",
|
||||
"MasqueradeBar.ViewingAs": "Viewing as: ",
|
||||
"MasqueradeBar.SubmitButton": "Submit",
|
||||
@@ -48,6 +37,20 @@
|
||||
"MasqueradeBar.UnknownError": "An unknown error occurred",
|
||||
"WidgetSidebar.lookingForChallengePrompt": "Looking for a new challenge?",
|
||||
"WidgetSidebar.findCoursesButton": "Find a course {arrow}",
|
||||
"ProductRecommendations.recommendationsHeading": "You might also like",
|
||||
"ProductRecommendations.executiveEducationHeading": "Executive Education",
|
||||
"ProductRecommendations.executiveEducationDescription": "Short Courses to develop leadership skills",
|
||||
"ProductRecommendations.bootcampHeading": "Boot Camp",
|
||||
"ProductRecommendations.bootcampDescription": "Intensive, hands-on, project based training",
|
||||
"ProductRecommendations.courseHeading": "Courses",
|
||||
"ProductRecommendations.courseDescription": "Find new interests and advance your career",
|
||||
"RecommendationsPanel.recommendationsFeatureText": "Personalized recommendations feature is not yet available. We are working hard on bringing it to your learner home in the near future.",
|
||||
"RecommendationsPanel.recommendationsAlertText": "Would you like to be alerted when it becomes available?",
|
||||
"RecommendationsPanel.recommendationsModalHeading": "Thank you for your interest!",
|
||||
"RecommendationsPanel.modalSkipButton": "Skip for now",
|
||||
"RecommendationsPanel.modalCountMeButton": "Count me in!",
|
||||
"learnerVariantDashboard.recommendedForYou": "Recommended For You",
|
||||
"RecommendationsPanel.seeAllRecommendationsButton": "See All Recommendations",
|
||||
"RecommendationsPanel.recommendationsHeading": "Recommendations for you",
|
||||
"RecommendationsPanel.popularCoursesHeading": "Popular courses",
|
||||
"RecommendationsPanel.exploreCoursesButton": "Explore courses"
|
||||
|
||||
@@ -4,12 +4,14 @@ import 'regenerator-runtime/runtime';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Switch, Redirect } from 'react-router-dom';
|
||||
import {
|
||||
Route, Navigate, Routes,
|
||||
} from 'react-router-dom';
|
||||
|
||||
import {
|
||||
AppProvider,
|
||||
ErrorPage,
|
||||
PageRoute,
|
||||
PageWrap,
|
||||
} from '@edx/frontend-platform/react';
|
||||
import store from 'data/store';
|
||||
import {
|
||||
@@ -31,12 +33,10 @@ subscribe(APP_READY, () => {
|
||||
ReactDOM.render(
|
||||
<AppProvider store={store}>
|
||||
<NoticesWrapper>
|
||||
<Switch>
|
||||
<PageRoute path="/">
|
||||
<App />
|
||||
</PageRoute>
|
||||
<Redirect to="/" />
|
||||
</Switch>
|
||||
<Routes>
|
||||
<Route path="/" element={<PageWrap><App /></PageWrap>} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</NoticesWrapper>
|
||||
</AppProvider>,
|
||||
document.getElementById('root'),
|
||||
|
||||
@@ -3,7 +3,7 @@ import '@testing-library/jest-dom';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
|
||||
import Enzyme from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
|
||||
@@ -14,6 +14,7 @@ jest.mock('react', () => ({
|
||||
useEffect: jest.fn((cb, prereqs) => ({ useEffect: { cb, prereqs } })),
|
||||
useMemo: jest.fn((cb, prereqs) => cb(prereqs)),
|
||||
useContext: jest.fn(context => context),
|
||||
useState: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('reselect', () => ({
|
||||
@@ -144,6 +145,8 @@ jest.mock('@edx/paragon', () => jest.requireActual('testUtils').mockNestedCompon
|
||||
Sheet: 'Sheet',
|
||||
StatefulButton: 'StatefulButton',
|
||||
TextFilter: 'TextFilter',
|
||||
Truncate: 'Truncate',
|
||||
Skeleton: 'Skeleton',
|
||||
Spinner: 'Spinner',
|
||||
PageBanner: 'PageBanner',
|
||||
Pagination: 'Pagination',
|
||||
|
||||
@@ -42,8 +42,9 @@ jest.unmock('react-redux');
|
||||
jest.unmock('reselect');
|
||||
jest.unmock('hooks');
|
||||
|
||||
jest.mock('containers/WidgetContainers/LoadedSidebar', () => 'loaded-widget-sidebar');
|
||||
jest.mock('containers/WidgetContainers/NoCoursesSidebar', () => 'no-courses-widget-sidebar');
|
||||
jest.mock('containers/WidgetContainers/LoadedSidebar', () => jest.fn(() => 'loaded-widget-sidebar'));
|
||||
jest.mock('containers/WidgetContainers/NoCoursesSidebar', () => jest.fn(() => 'no-courses-widget-sidebar'));
|
||||
jest.mock('containers/WidgetContainers/WidgetFooter', () => 'product-recommendations-footer');
|
||||
jest.mock('components/NoticesWrapper', () => 'notices-wrapper');
|
||||
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
@@ -60,6 +61,10 @@ jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
getLoginRedirectUrl: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('ExperimentContext', () => ({
|
||||
ExperimentProvider: 'div'
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-enterprise-hotjar', () => ({
|
||||
initializeHotjar: jest.fn(),
|
||||
}));
|
||||
|
||||
@@ -18,8 +18,8 @@ exports[`LookingForChallengeWidget snapshots default 1`] = `
|
||||
<h5>
|
||||
<Hyperlink
|
||||
className="d-flex align-items-center"
|
||||
destination="course-search-url"
|
||||
onClick={[MockFunction track.findCoursesWidgetClicked('course-search-url')]}
|
||||
destination="http://localhost:18000/course-search-url"
|
||||
onClick={[MockFunction track.findCoursesWidgetClicked('http://localhost:18000/course-search-url')]}
|
||||
variant="brand"
|
||||
>
|
||||
<format-message-function
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ArrowForward } from '@edx/paragon/icons';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import moreCoursesSVG from 'assets/more-courses-sidewidget.svg';
|
||||
import { baseAppUrl } from 'data/services/lms/urls';
|
||||
|
||||
import track from '../RecommendationsPanel/track';
|
||||
import messages from './messages';
|
||||
@@ -29,8 +30,8 @@ export const LookingForChallengeWidget = () => {
|
||||
<h5>
|
||||
<Hyperlink
|
||||
variant="brand"
|
||||
destination={courseSearchUrl}
|
||||
onClick={track.findCoursesWidgetClicked(courseSearchUrl)}
|
||||
destination={baseAppUrl(courseSearchUrl)}
|
||||
onClick={track.findCoursesWidgetClicked(baseAppUrl(courseSearchUrl))}
|
||||
className="d-flex align-items-center"
|
||||
>
|
||||
{formatMessage(messages.findCoursesButton, { arrow: arrowIcon })}
|
||||
|
||||
@@ -5,7 +5,7 @@ import LookingForChallengeWidget from '.';
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
usePlatformSettingsData: () => ({
|
||||
courseSearchUrl: 'course-search-url',
|
||||
courseSearchUrl: 'http://localhost:18000/course-search-url',
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ProductRecommendations matches snapshot 1`] = `
|
||||
<LoadedView
|
||||
crossProductCourses={
|
||||
Array [
|
||||
Object {
|
||||
"courseRunKey": "course-v1:Test+Course+2022T2",
|
||||
"courseType": "executive-education-2u",
|
||||
"image": Object {
|
||||
"src": "https://www.image-2.com/ed79a49b-64c1-48d2-afdc-054bf921e38d-6a76ceb47dea.small.jpg",
|
||||
},
|
||||
"marketingUrl": "https://www.edx.org/course/some-course?utm_source=source",
|
||||
"owners": Array [
|
||||
Object {
|
||||
"key": "HarvardX",
|
||||
"logoImageUrl": "http://www.image.com/ef72daf3-c9a1-4c00-ba37-b3514392bdcf-8839c516815a.png",
|
||||
"name": "Harvard University",
|
||||
},
|
||||
],
|
||||
"title": "Introduction to Computer Science",
|
||||
},
|
||||
Object {
|
||||
"courseRunKey": "course-v1:Test+Course+2022T2",
|
||||
"courseType": "bootcamp-2u",
|
||||
"image": Object {
|
||||
"src": "https://www.image-2.com/ed79a49b-64c1-48d2-afdc-054bf921e38d-6a76ceb47dea.small.jpg",
|
||||
},
|
||||
"marketingUrl": "https://www.edx.org/course/some-course?utm_source=source",
|
||||
"owners": Array [
|
||||
Object {
|
||||
"key": "HarvardX",
|
||||
"logoImageUrl": "http://www.image.com/ef72daf3-c9a1-4c00-ba37-b3514392bdcf-8839c516815a.png",
|
||||
"name": "Harvard University",
|
||||
},
|
||||
],
|
||||
"title": "Introduction to Computer Science",
|
||||
},
|
||||
]
|
||||
}
|
||||
openCourses={
|
||||
Array [
|
||||
Object {
|
||||
"courseRunKey": "course-v1:Test+Course+2022T2",
|
||||
"courseType": "verified-audit",
|
||||
"image": Object {
|
||||
"src": "https://www.image-2.com/ed79a49b-64c1-48d2-afdc-054bf921e38d-6a76ceb47dea.small.jpg",
|
||||
},
|
||||
"marketingUrl": "https://www.edx.org/course/some-course?utm_source=source",
|
||||
"owners": Array [
|
||||
Object {
|
||||
"key": "HarvardX",
|
||||
"logoImageUrl": "http://www.image.com/ef72daf3-c9a1-4c00-ba37-b3514392bdcf-8839c516815a.png",
|
||||
"name": "Harvard University",
|
||||
},
|
||||
],
|
||||
"title": "Introduction to Computer Science",
|
||||
},
|
||||
Object {
|
||||
"courseRunKey": "course-v1:Test+Course+2022T2",
|
||||
"courseType": "audit",
|
||||
"image": Object {
|
||||
"src": "https://www.image-2.com/ed79a49b-64c1-48d2-afdc-054bf921e38d-6a76ceb47dea.small.jpg",
|
||||
},
|
||||
"marketingUrl": "https://www.edx.org/course/some-course?utm_source=source",
|
||||
"owners": Array [
|
||||
Object {
|
||||
"key": "HarvardX",
|
||||
"logoImageUrl": "http://www.image.com/ef72daf3-c9a1-4c00-ba37-b3514392bdcf-8839c516815a.png",
|
||||
"name": "Harvard University",
|
||||
},
|
||||
],
|
||||
"title": "Introduction to Computer Science",
|
||||
},
|
||||
Object {
|
||||
"courseRunKey": "course-v1:Test+Course+2022T2",
|
||||
"courseType": "verified",
|
||||
"image": Object {
|
||||
"src": "https://www.image-2.com/ed79a49b-64c1-48d2-afdc-054bf921e38d-6a76ceb47dea.small.jpg",
|
||||
},
|
||||
"marketingUrl": "https://www.edx.org/course/some-course?utm_source=source",
|
||||
"owners": Array [
|
||||
Object {
|
||||
"key": "HarvardX",
|
||||
"logoImageUrl": "http://www.image.com/ef72daf3-c9a1-4c00-ba37-b3514392bdcf-8839c516815a.png",
|
||||
"name": "Harvard University",
|
||||
},
|
||||
],
|
||||
"title": "Introduction to Computer Science",
|
||||
},
|
||||
Object {
|
||||
"courseRunKey": "course-v1:Test+Course+2022T2",
|
||||
"courseType": "course",
|
||||
"image": Object {
|
||||
"src": "https://www.image-2.com/ed79a49b-64c1-48d2-afdc-054bf921e38d-6a76ceb47dea.small.jpg",
|
||||
},
|
||||
"marketingUrl": "https://www.edx.org/course/some-course?utm_source=source",
|
||||
"owners": Array [
|
||||
Object {
|
||||
"key": "HarvardX",
|
||||
"logoImageUrl": "http://www.image.com/ef72daf3-c9a1-4c00-ba37-b3514392bdcf-8839c516815a.png",
|
||||
"name": "Harvard University",
|
||||
},
|
||||
],
|
||||
"title": "Introduction to Computer Science",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
`;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user