Compare commits
80 Commits
open-relea
...
mfrank/rem
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01a13380f2 | ||
|
|
afe386566b | ||
|
|
d5da8ba62f | ||
|
|
24a3a2de65 | ||
|
|
40f8b0e960 | ||
|
|
d1813d3dcd | ||
|
|
d5b02fbbb0 | ||
|
|
b3620a7832 | ||
|
|
5829e25fed | ||
|
|
aa041245af | ||
|
|
17aa646856 | ||
|
|
7ef5d5b034 | ||
|
|
68a46ac023 | ||
|
|
6310cc0452 | ||
|
|
9f52c61e4f | ||
|
|
6a62301c1c | ||
|
|
e61eaa8264 | ||
|
|
c906ce0d3a | ||
|
|
e9a1e3e40d | ||
|
|
f149f2c8cf | ||
|
|
3ee7c62119 | ||
|
|
a5d1cb380d | ||
|
|
e3b4e0956a | ||
|
|
eb427f3772 | ||
|
|
a53c167558 | ||
|
|
61476422bb | ||
|
|
5b6c4004c7 | ||
|
|
d7bd32aae3 | ||
|
|
c14496ade9 | ||
|
|
4b117c7882 | ||
|
|
0162a62c56 | ||
|
|
79ad701eca | ||
|
|
3150c110a1 | ||
|
|
9c3264c8a2 | ||
|
|
95a35e17dc | ||
|
|
aa7296c3cd | ||
|
|
89ae34c874 | ||
|
|
62a9cb0045 | ||
|
|
a96c8fc6ab | ||
|
|
c0ad27077f | ||
|
|
08ead35644 | ||
|
|
362bb8b3cf | ||
|
|
20eebf2f28 | ||
|
|
7e8dad41ec | ||
|
|
f6af646b80 | ||
|
|
3b25d04752 | ||
|
|
31eafb30ba | ||
|
|
1705926f52 | ||
|
|
f1128d63d7 | ||
|
|
c9866af227 | ||
|
|
a2ccda7b30 | ||
|
|
54cf4bb8fd | ||
|
|
229436cddf | ||
|
|
e94dd56fb1 | ||
|
|
3becef3468 | ||
|
|
3570ead725 | ||
|
|
17c5fd09f9 | ||
|
|
506f8f795f | ||
|
|
e73880b442 | ||
|
|
08050f458e | ||
|
|
75f19a28b7 | ||
|
|
ecc4c4c2e0 | ||
|
|
28da100ca2 | ||
|
|
0fbac0715d | ||
|
|
49f9e6e424 | ||
|
|
17eff1da7b | ||
|
|
0fcaa64a6e | ||
|
|
506d3f655c | ||
|
|
37bb54d28e | ||
|
|
1d53ef6153 | ||
|
|
59d90f5dc8 | ||
|
|
61d881b1cd | ||
|
|
77adb60167 | ||
|
|
b2b199e0e2 | ||
|
|
92f712d670 | ||
|
|
11b7e48080 | ||
|
|
a051d712dc | ||
|
|
8c76b5c689 | ||
|
|
7fccd94d6b | ||
|
|
c405bc63ea |
1
.env
1
.env
@@ -40,5 +40,4 @@ ACCOUNT_SETTINGS_URL=''
|
||||
ACCOUNT_PROFILE_URL=''
|
||||
ENABLE_NOTICES=''
|
||||
CAREER_LINK_URL=''
|
||||
OPTIMIZELY_FULL_STACK_SDK_KEY=''
|
||||
ENABLE_EDX_PERSONAL_DASHBOARD=false
|
||||
|
||||
@@ -46,5 +46,4 @@ ACCOUNT_SETTINGS_URL='http://localhost:1997'
|
||||
ACCOUNT_PROFILE_URL='http://localhost:1995'
|
||||
ENABLE_NOTICES=''
|
||||
CAREER_LINK_URL=''
|
||||
OPTIMIZELY_FULL_STACK_SDK_KEY=''
|
||||
ENABLE_EDX_PERSONAL_DASHBOARD=false
|
||||
|
||||
@@ -45,5 +45,4 @@ 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'
|
||||
ENABLE_EDX_PERSONAL_DASHBOARD=true
|
||||
|
||||
7
.github/CODEOWNERS
vendored
7
.github/CODEOWNERS
vendored
@@ -1,8 +1 @@
|
||||
# Root app is developed and owned by Aurora
|
||||
* @openedx/2U-aperture
|
||||
|
||||
# WIDGETS and experiments are developed and owned by separate teams below
|
||||
|
||||
# Recommendations panel
|
||||
/src/widgets/RecommendationsPanel @openedx/2U-vanguards
|
||||
/src/widgets/LookingForChallengeWidget @openedx/2U-vanguards
|
||||
|
||||
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@@ -13,13 +13,13 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Nodejs Env
|
||||
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup Nodejs
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VER }}
|
||||
|
||||
@@ -39,7 +39,10 @@ jobs:
|
||||
run: npm run build
|
||||
|
||||
- name: Run Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: true
|
||||
|
||||
- name: Send failure notification
|
||||
if: ${{ failure() }}
|
||||
|
||||
2
.github/workflows/lockfileversion-check.yml
vendored
2
.github/workflows/lockfileversion-check.yml
vendored
@@ -10,4 +10,4 @@ on:
|
||||
|
||||
jobs:
|
||||
version-check:
|
||||
uses: openedx/.github/.github/workflows/lockfileversion-check-v3.yml@master
|
||||
uses: openedx/.github/.github/workflows/lockfile-check.yml@master
|
||||
|
||||
6
.github/workflows/npm-publish.yml
vendored
6
.github/workflows/npm-publish.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# TODO: MAKE GITHUB ISSUE FOR THIS — unused workflow
|
||||
|
||||
name: Release CI
|
||||
on:
|
||||
push:
|
||||
@@ -10,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -18,7 +20,7 @@ jobs:
|
||||
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VER }}
|
||||
|
||||
|
||||
@@ -69,6 +69,5 @@ module.exports = {
|
||||
ACCOUNT_PROFILE_URL: 'http://localhost:1995',
|
||||
ENABLE_NOTICES: '',
|
||||
CAREER_LINK_URL: '',
|
||||
OPTIMIZELY_FULL_STACK_SDK_KEY: '',
|
||||
EXPERIMENT_08_23_VAN_PAINTED_DOOR: true,
|
||||
};
|
||||
|
||||
57
junk.json
Normal file
57
junk.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
|
||||
"@edx/browserslist-config": "^1.1.0",
|
||||
"@edx/frontend-component-header": "^5.3.1",
|
||||
"@edx/frontend-enterprise-hotjar": "3.0.0",
|
||||
"@edx/frontend-platform": "8.1.1",
|
||||
"@edx/openedx-atlas": "^0.6.0",
|
||||
"@edx/react-unit-test-utils": "3.0.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@openedx/frontend-plugin-framework": "^1.2.0",
|
||||
"@openedx/frontend-slot-footer": "^1.0.2",
|
||||
"@openedx/paragon": "^22.2.2",
|
||||
"REMOVED_@redux-beacon/segment": "^1.1.0",
|
||||
"@redux-devtools/extension": "3.3.0",
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"REMOVED_@testing-library/user-event": "^13.5.0",
|
||||
"UNUSED_axios": "^0.28.0",
|
||||
"classnames": "^2.3.1",
|
||||
"core-js": "3.38.0",
|
||||
"REMOVED_dompurify": "^2.3.1",
|
||||
"UNUSED_email-prop-type": "^3.0.1",
|
||||
"UNUSED_file-saver": "^2.0.5",
|
||||
"UNUSED_filesize": "^8.0.6",
|
||||
"font-awesome": "4.7.0",
|
||||
"UNUSED_history": "5.3.0",
|
||||
"UNUSED_html-react-parser": "^1.3.0",
|
||||
"DEV_jest": "^29.7.0",
|
||||
"DEV_jest-environment-jsdom": "29.7.0",
|
||||
"DEV_jest-when": "^3.6.0",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"prop-types": "15.8.1",
|
||||
"query-string": "7.1.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-intl": "6.6.8",
|
||||
"UNUSED_react-pdf": "^7.0.0",
|
||||
"react-redux": "^7.2.4",
|
||||
"react-router-dom": "6.26.0",
|
||||
"react-share": "^4.4.0",
|
||||
"react-zendesk": "^0.1.13",
|
||||
"redux": "4.2.1",
|
||||
"UNUSED_redux-beacon": "^2.1.0",
|
||||
"redux-logger": "3.0.6",
|
||||
"redux-thunk": "2.4.2",
|
||||
"regenerator-runtime": "^0.14.0",
|
||||
"reselect": "^4.0.0",
|
||||
"UNUSED_universal-cookie": "^4.0.4",
|
||||
"UNUSED_?util": "^0.12.4",
|
||||
"UNUSED_whatwg-fetch": "^3.6.2"
|
||||
}
|
||||
}
|
||||
17826
package-lock.json
generated
17826
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
53
package.json
53
package.json
@@ -28,68 +28,55 @@
|
||||
"dependencies": {
|
||||
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
|
||||
"@edx/browserslist-config": "^1.1.0",
|
||||
"@edx/frontend-component-header": "^5.3.1",
|
||||
"@edx/frontend-enterprise-hotjar": "3.0.0",
|
||||
"@edx/frontend-platform": "^7.1.4",
|
||||
"@edx/frontend-platform": "8.1.1",
|
||||
"@edx/openedx-atlas": "^0.6.0",
|
||||
"@edx/react-unit-test-utils": "2.0.0",
|
||||
"@edx/react-unit-test-utils": "3.0.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@openedx/frontend-plugin-framework": "^1.1.2",
|
||||
"@openedx/frontend-plugin-framework": "^1.2.0",
|
||||
"@openedx/frontend-slot-footer": "^1.0.2",
|
||||
"@openedx/paragon": "^22.2.2",
|
||||
"@optimizely/react-sdk": "^2.9.2",
|
||||
"@redux-beacon/segment": "^1.1.0",
|
||||
"@redux-devtools/extension": "3.3.0",
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"axios": "^0.28.0",
|
||||
"classnames": "^2.3.1",
|
||||
"core-js": "3.16.2",
|
||||
"dompurify": "^2.3.1",
|
||||
"email-prop-type": "^3.0.1",
|
||||
"file-saver": "^2.0.5",
|
||||
"filesize": "^8.0.6",
|
||||
"core-js": "3.38.0",
|
||||
"font-awesome": "4.7.0",
|
||||
"history": "5.0.1",
|
||||
"html-react-parser": "^1.3.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-when": "^3.6.0",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"prop-types": "^15.7.2",
|
||||
"query-string": "7.0.1",
|
||||
"prop-types": "15.8.1",
|
||||
"query-string": "7.1.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-intl": "6.4.7",
|
||||
"react-pdf": "^7.0.0",
|
||||
"react-intl": "6.6.8",
|
||||
"react-redux": "^7.2.4",
|
||||
"react-router-dom": "6.15.0",
|
||||
"react-router-dom": "6.26.0",
|
||||
"react-share": "^4.4.0",
|
||||
"react-zendesk": "^0.1.13",
|
||||
"redux": "4.1.1",
|
||||
"redux-beacon": "^2.1.0",
|
||||
"redux-devtools-extension": "2.13.9",
|
||||
"redux": "4.2.1",
|
||||
"redux-logger": "3.0.6",
|
||||
"redux-thunk": "2.3.0",
|
||||
"regenerator-runtime": "^0.13.9",
|
||||
"redux-thunk": "2.4.2",
|
||||
"regenerator-runtime": "^0.14.0",
|
||||
"reselect": "^4.0.0",
|
||||
"universal-cookie": "^4.0.4",
|
||||
"util": "^0.12.4",
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
"util": "^0.12.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@edx/reactifex": "^2.1.1",
|
||||
"@openedx/frontend-build": "13.1.4",
|
||||
"@openedx/frontend-build": "14.0.15",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^12.1.0",
|
||||
"axios-mock-adapter": "^1.20.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"fetch-mock": "^9.11.0",
|
||||
"copy-webpack-plugin": "^12.0.0",
|
||||
"husky": "^7.0.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest-expect-message": "^1.0.2",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-expect-message": "^1.1.3",
|
||||
"jest-when": "^3.6.0",
|
||||
"react-dev-utils": "^11.0.4",
|
||||
"react-test-renderer": "^17.0.2",
|
||||
"redux-mock-store": "^1.5.4",
|
||||
|
||||
19
src/App.jsx
19
src/App.jsx
@@ -18,7 +18,6 @@ import {
|
||||
import { reduxHooks } from 'hooks';
|
||||
import Dashboard from 'containers/Dashboard';
|
||||
import ZendeskFab from 'components/ZendeskFab';
|
||||
import { ExperimentProvider } from 'ExperimentContext';
|
||||
|
||||
import track from 'tracking';
|
||||
|
||||
@@ -42,19 +41,6 @@ export const App = () => {
|
||||
const { supportEmail } = reduxHooks.usePlatformSettingsData();
|
||||
const loadData = reduxHooks.useLoadData();
|
||||
|
||||
const optimizelyScript = () => {
|
||||
if (getConfig().OPTIMIZELY_URL) {
|
||||
return <script src={getConfig().OPTIMIZELY_URL} />;
|
||||
} if (getConfig().OPTIMIZELY_PROJECT_ID) {
|
||||
return (
|
||||
<script
|
||||
src={`${getConfig().MARKETING_SITE_BASE_URL}/optimizelyjs/${getConfig().OPTIMIZELY_PROJECT_ID}.js`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (authenticatedUser?.administrator || getConfig().NODE_ENV === 'development') {
|
||||
window.loadEmptyData = () => {
|
||||
@@ -91,7 +77,6 @@ export const App = () => {
|
||||
<Helmet>
|
||||
<title>{formatMessage(messages.pageTitle)}</title>
|
||||
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
|
||||
{optimizelyScript()}
|
||||
</Helmet>
|
||||
<div>
|
||||
<AppWrapper>
|
||||
@@ -103,9 +88,7 @@ export const App = () => {
|
||||
<ErrorPage message={formatMessage(messages.errorMessage, { supportEmail })} />
|
||||
</Alert>
|
||||
) : (
|
||||
<ExperimentProvider>
|
||||
<Dashboard />
|
||||
</ExperimentProvider>
|
||||
<Dashboard />
|
||||
)}
|
||||
</main>
|
||||
</AppWrapper>
|
||||
|
||||
@@ -9,6 +9,7 @@ $fa-font-path: "~font-awesome/fonts";
|
||||
|
||||
$input-focus-box-shadow: $input-box-shadow; // hack to get upgrade to paragon 4.0.0 to work
|
||||
|
||||
@import "~@edx/frontend-component-header/dist/index";
|
||||
@import "~@edx/frontend-component-footer/dist/_footer";
|
||||
|
||||
.text-ellipsis {
|
||||
|
||||
@@ -10,7 +10,6 @@ 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';
|
||||
|
||||
@@ -19,9 +18,6 @@ jest.mock('@edx/frontend-component-footer', () => ({ FooterSlot: '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',
|
||||
@@ -79,11 +75,10 @@ describe('App router component', () => {
|
||||
it('loads dashboard', () => {
|
||||
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);
|
||||
const dashboard = main.children[0];
|
||||
expect(dashboard.type).toEqual('Dashboard');
|
||||
expect(
|
||||
expProvider.matches(shallow(<ExperimentProvider><Dashboard /></ExperimentProvider>)),
|
||||
dashboard.matches(shallow(<Dashboard />)),
|
||||
).toEqual(true);
|
||||
});
|
||||
});
|
||||
@@ -97,11 +92,10 @@ describe('App router component', () => {
|
||||
it('loads dashboard', () => {
|
||||
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);
|
||||
const dashboard = main.children[0];
|
||||
expect(dashboard.type).toEqual('Dashboard');
|
||||
expect(
|
||||
expProvider.matches(shallow(<ExperimentProvider><Dashboard /></ExperimentProvider>)),
|
||||
dashboard.matches(shallow(<Dashboard />)),
|
||||
).toEqual(true);
|
||||
});
|
||||
});
|
||||
@@ -115,11 +109,10 @@ describe('App router component', () => {
|
||||
it('loads dashboard', () => {
|
||||
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);
|
||||
const dashboard = main.children[0];
|
||||
expect(dashboard.type).toEqual('Dashboard');
|
||||
expect(
|
||||
expProvider.matches(shallow(<ExperimentProvider><Dashboard /></ExperimentProvider>)),
|
||||
dashboard.matches(shallow(<Dashboard />)),
|
||||
).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useWindowSize, breakpoints } from '@openedx/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 };
|
||||
@@ -1,122 +0,0 @@
|
||||
import React from 'react';
|
||||
import { waitFor, render } from '@testing-library/react';
|
||||
import { useWindowSize } from '@openedx/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('successful 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('unsuccessful 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();
|
||||
|
||||
render(
|
||||
<ExperimentProvider>
|
||||
<TestComponent />
|
||||
</ExperimentProvider>,
|
||||
);
|
||||
|
||||
expect(countryCodeSpy).toHaveBeenCalledWith(state.setState.countryCode);
|
||||
state.expectInitializedWith(state.keys.countryCode, null);
|
||||
state.expectInitializedWith(state.keys.experiment, { isExperimentActive: false, inRecommendationsVariant: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -51,9 +51,7 @@ exports[`App router component component no network failure snapshot 1`] = `
|
||||
<AppWrapper>
|
||||
<LearnerDashboardHeader />
|
||||
<main>
|
||||
<ExperimentProvider>
|
||||
<Dashboard />
|
||||
</ExperimentProvider>
|
||||
<Dashboard />
|
||||
</main>
|
||||
</AppWrapper>
|
||||
<FooterSlot />
|
||||
@@ -75,17 +73,12 @@ exports[`App router component component no network failure with optimizely proje
|
||||
rel="shortcut icon"
|
||||
type="image/x-icon"
|
||||
/>
|
||||
<script
|
||||
src="undefined/optimizelyjs/fakeId.js"
|
||||
/>
|
||||
</HelmetWrapper>
|
||||
<div>
|
||||
<AppWrapper>
|
||||
<LearnerDashboardHeader />
|
||||
<main>
|
||||
<ExperimentProvider>
|
||||
<Dashboard />
|
||||
</ExperimentProvider>
|
||||
<Dashboard />
|
||||
</main>
|
||||
</AppWrapper>
|
||||
<FooterSlot />
|
||||
@@ -107,17 +100,12 @@ exports[`App router component component no network failure with optimizely url s
|
||||
rel="shortcut icon"
|
||||
type="image/x-icon"
|
||||
/>
|
||||
<script
|
||||
src="fake.url"
|
||||
/>
|
||||
</HelmetWrapper>
|
||||
<div>
|
||||
<AppWrapper>
|
||||
<LearnerDashboardHeader />
|
||||
<main>
|
||||
<ExperimentProvider>
|
||||
<Dashboard />
|
||||
</ExperimentProvider>
|
||||
<Dashboard />
|
||||
</main>
|
||||
</AppWrapper>
|
||||
<FooterSlot />
|
||||
|
||||
@@ -9,7 +9,7 @@ exports[`app registry subscribe: APP_INIT_ERROR. snapshot: displays an ErrorPag
|
||||
exports[`app registry subscribe: APP_READY. links App to root element 1`] = `
|
||||
<AppProvider
|
||||
store={
|
||||
Object {
|
||||
{
|
||||
"redux": "store",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,23 +5,23 @@ exports[`ZendeskFab snapshot 1`] = `
|
||||
cookies={true}
|
||||
defer={true}
|
||||
webWidget={
|
||||
Object {
|
||||
"answerBot": Object {
|
||||
"avatar": Object {
|
||||
"name": Object {
|
||||
{
|
||||
"answerBot": {
|
||||
"avatar": {
|
||||
"name": {
|
||||
"*": "edX Support",
|
||||
},
|
||||
"url": "https://edx-cdn.org/v3/prod/favicon.ico",
|
||||
},
|
||||
"contactOnlyAfterQuery": true,
|
||||
"suppress": false,
|
||||
"title": Object {
|
||||
"title": {
|
||||
"*": "edX Support",
|
||||
},
|
||||
},
|
||||
"chat": Object {
|
||||
"departments": Object {
|
||||
"enabled": Array [
|
||||
"chat": {
|
||||
"departments": {
|
||||
"enabled": [
|
||||
"account settings",
|
||||
"billing and payments",
|
||||
"certificates",
|
||||
@@ -33,17 +33,17 @@ exports[`ZendeskFab snapshot 1`] = `
|
||||
},
|
||||
"suppress": false,
|
||||
},
|
||||
"contactForm": Object {
|
||||
"contactForm": {
|
||||
"attachments": true,
|
||||
"selectTicketForm": Object {
|
||||
"selectTicketForm": {
|
||||
"*": "Please choose your request type:",
|
||||
},
|
||||
"ticketForms": Array [
|
||||
Object {
|
||||
"fields": Array [
|
||||
Object {
|
||||
"ticketForms": [
|
||||
{
|
||||
"fields": [
|
||||
{
|
||||
"id": "description",
|
||||
"prefill": Object {
|
||||
"prefill": {
|
||||
"*": "",
|
||||
},
|
||||
},
|
||||
@@ -53,10 +53,10 @@ exports[`ZendeskFab snapshot 1`] = `
|
||||
},
|
||||
],
|
||||
},
|
||||
"contactOptions": Object {
|
||||
"contactOptions": {
|
||||
"enabled": false,
|
||||
},
|
||||
"helpCenter": Object {
|
||||
"helpCenter": {
|
||||
"originalArticleButton": true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ exports[`BeginCourseButton snapshot disabled snapshot 1`] = `
|
||||
disabled={true}
|
||||
href="#"
|
||||
onClick={
|
||||
Object {
|
||||
"trackCourseEvent": Object {
|
||||
{
|
||||
"trackCourseEvent": {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.enterCourseClicked],
|
||||
"upgradeUrl": "home-urlexec-ed-tracking-path=cardId",
|
||||
@@ -25,8 +25,8 @@ exports[`BeginCourseButton snapshot enabled snapshot 1`] = `
|
||||
disabled={false}
|
||||
href="#"
|
||||
onClick={
|
||||
Object {
|
||||
"trackCourseEvent": Object {
|
||||
{
|
||||
"trackCourseEvent": {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.enterCourseClicked],
|
||||
"upgradeUrl": "home-urlexec-ed-tracking-path=cardId",
|
||||
|
||||
@@ -6,8 +6,8 @@ exports[`ResumeButton snapshot disabled snapshot 1`] = `
|
||||
disabled={true}
|
||||
href="#"
|
||||
onClick={
|
||||
Object {
|
||||
"trackCourseEvent": Object {
|
||||
{
|
||||
"trackCourseEvent": {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.enterCourseClicked],
|
||||
"upgradeUrl": "resume-urlexec-ed-tracking-path=cardId",
|
||||
@@ -25,8 +25,8 @@ exports[`ResumeButton snapshot enabled snapshot 1`] = `
|
||||
disabled={false}
|
||||
href="#"
|
||||
onClick={
|
||||
Object {
|
||||
"trackCourseEvent": Object {
|
||||
{
|
||||
"trackCourseEvent": {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.enterCourseClicked],
|
||||
"upgradeUrl": "resume-urlexec-ed-tracking-path=cardId",
|
||||
|
||||
@@ -7,8 +7,8 @@ exports[`UpgradeButton snapshot can upgrade 1`] = `
|
||||
href="upgradeUrl"
|
||||
iconBefore={[MockFunction icons.Locked]}
|
||||
onClick={
|
||||
Object {
|
||||
"trackCourseEvent": Object {
|
||||
{
|
||||
"trackCourseEvent": {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.trackUpgradeClicked],
|
||||
"upgradeUrl": "upgradeUrl",
|
||||
|
||||
@@ -6,8 +6,8 @@ exports[`ViewCourseButton learner can view course 1`] = `
|
||||
disabled={false}
|
||||
href="#"
|
||||
onClick={
|
||||
Object {
|
||||
"trackCourseEvent": Object {
|
||||
{
|
||||
"trackCourseEvent": {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.enterCourseClicked],
|
||||
"upgradeUrl": "homeUrl",
|
||||
@@ -25,8 +25,8 @@ exports[`ViewCourseButton learner cannot view course 1`] = `
|
||||
disabled={true}
|
||||
href="#"
|
||||
onClick={
|
||||
Object {
|
||||
"trackCourseEvent": Object {
|
||||
{
|
||||
"trackCourseEvent": {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.enterCourseClicked],
|
||||
"upgradeUrl": "homeUrl",
|
||||
|
||||
@@ -10,14 +10,14 @@ exports[`CreditBanner component render with error state snapshot 1`] = `
|
||||
>
|
||||
<format-message-function
|
||||
message={
|
||||
Object {
|
||||
{
|
||||
"defaultMessage": "An error occurred with this transaction. For help, contact {supportEmailLink}.",
|
||||
"description": "",
|
||||
"id": "learner-dash.courseCard.banners.credit.error",
|
||||
}
|
||||
}
|
||||
values={
|
||||
Object {
|
||||
{
|
||||
"supportEmailLink": <MailtoLink
|
||||
to="test-support-email"
|
||||
>
|
||||
|
||||
@@ -27,8 +27,8 @@ exports[`CreditContent component render with action snapshot 1`] = `
|
||||
</ActionRow>
|
||||
<CreditRequestForm
|
||||
requestData={
|
||||
Object {
|
||||
"parameters": Object {
|
||||
{
|
||||
"parameters": {
|
||||
"key1": "val1",
|
||||
},
|
||||
"url": "test-request-data-url",
|
||||
@@ -48,8 +48,8 @@ exports[`CreditContent component render without action snapshot 1`] = `
|
||||
</div>
|
||||
<CreditRequestForm
|
||||
requestData={
|
||||
Object {
|
||||
"parameters": Object {
|
||||
{
|
||||
"parameters": {
|
||||
"key1": "val1",
|
||||
},
|
||||
"url": "test-request-data-url",
|
||||
|
||||
@@ -13,12 +13,12 @@ exports[`RelatedProgramsBanner render with programs 1`] = `
|
||||
</span>
|
||||
<ProgramsList
|
||||
programs={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"title": "Program 1",
|
||||
"url": "http://example.com/program1",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"title": "Program 2",
|
||||
"url": "http://example.com/program2",
|
||||
},
|
||||
|
||||
@@ -43,14 +43,14 @@ exports[`CertificateBanner snapshot is restricted and verified with billing emai
|
||||
|
||||
<format-message-function
|
||||
message={
|
||||
Object {
|
||||
{
|
||||
"defaultMessage": "If you would like a refund on your Certificate of Achievement, please contact our billing address {billingEmail}",
|
||||
"description": "Message to learners to contact billing for certificate refunds",
|
||||
"id": "learner-dash.courseCard.banners.certificateRefundContactBilling",
|
||||
}
|
||||
}
|
||||
values={
|
||||
Object {
|
||||
{
|
||||
"billingEmail": <MailtoLink
|
||||
to="billing@email"
|
||||
>
|
||||
@@ -68,14 +68,14 @@ exports[`CertificateBanner snapshot is restricted and verified with support and
|
||||
>
|
||||
<format-message-function
|
||||
message={
|
||||
Object {
|
||||
{
|
||||
"defaultMessage": "Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting {supportEmail}.",
|
||||
"description": "Restricted certificate warning message",
|
||||
"id": "learner-dash.courseCard.banners.certificateRestricted",
|
||||
}
|
||||
}
|
||||
values={
|
||||
Object {
|
||||
{
|
||||
"supportEmail": <MailtoLink
|
||||
to="suport@email"
|
||||
>
|
||||
@@ -87,14 +87,14 @@ exports[`CertificateBanner snapshot is restricted and verified with support and
|
||||
|
||||
<format-message-function
|
||||
message={
|
||||
Object {
|
||||
{
|
||||
"defaultMessage": "If you would like a refund on your Certificate of Achievement, please contact our billing address {billingEmail}",
|
||||
"description": "Message to learners to contact billing for certificate refunds",
|
||||
"id": "learner-dash.courseCard.banners.certificateRefundContactBilling",
|
||||
}
|
||||
}
|
||||
values={
|
||||
Object {
|
||||
{
|
||||
"billingEmail": <MailtoLink
|
||||
to="billing@email"
|
||||
>
|
||||
@@ -112,14 +112,14 @@ exports[`CertificateBanner snapshot is restricted and verified with support emai
|
||||
>
|
||||
<format-message-function
|
||||
message={
|
||||
Object {
|
||||
{
|
||||
"defaultMessage": "Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting {supportEmail}.",
|
||||
"description": "Restricted certificate warning message",
|
||||
"id": "learner-dash.courseCard.banners.certificateRestricted",
|
||||
}
|
||||
}
|
||||
values={
|
||||
Object {
|
||||
{
|
||||
"supportEmail": <MailtoLink
|
||||
to="suport@email"
|
||||
>
|
||||
@@ -147,14 +147,14 @@ exports[`CertificateBanner snapshot is restricted with support email 1`] = `
|
||||
>
|
||||
<format-message-function
|
||||
message={
|
||||
Object {
|
||||
{
|
||||
"defaultMessage": "Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting {supportEmail}.",
|
||||
"description": "Restricted certificate warning message",
|
||||
"id": "learner-dash.courseCard.banners.certificateRestricted",
|
||||
}
|
||||
}
|
||||
values={
|
||||
Object {
|
||||
{
|
||||
"supportEmail": <MailtoLink
|
||||
to="suport@email"
|
||||
>
|
||||
|
||||
@@ -4,14 +4,14 @@ exports[`EntitlementBanner snapshot: expiration warning 1`] = `
|
||||
<Banner>
|
||||
<format-message-function
|
||||
message={
|
||||
Object {
|
||||
{
|
||||
"defaultMessage": "You must {selectSessionButton} by {changeDeadline} to access the course.",
|
||||
"description": "Entitlement course message when the entitlement is expiring soon.",
|
||||
"id": "learner-dash.courseCard.banners.entitlementExpiringSoon",
|
||||
}
|
||||
}
|
||||
values={
|
||||
Object {
|
||||
{
|
||||
"changeDeadline": "11/11/2022",
|
||||
"selectSessionButton": <Button
|
||||
className="m-0 p-0"
|
||||
@@ -33,14 +33,14 @@ exports[`EntitlementBanner snapshot: no sessions available 1`] = `
|
||||
>
|
||||
<format-message-function
|
||||
message={
|
||||
Object {
|
||||
{
|
||||
"defaultMessage": "There are no sessions available at the moment. The course team will create new sessions soon. If no sessions appear, please contact {emailLink} for information.",
|
||||
"description": "Entitlement course message when no sessions are available",
|
||||
"id": "learner-dash.courseCard.banners.entitlementUnavailable",
|
||||
}
|
||||
}
|
||||
values={
|
||||
Object {
|
||||
{
|
||||
"emailLink": <MailtoLink
|
||||
to="test-support-email"
|
||||
>
|
||||
|
||||
@@ -17,7 +17,7 @@ exports[`CourseCardMenu render show dropdown hide unenroll item and disable emai
|
||||
<SocialShareMenu
|
||||
cardId="test-card-id"
|
||||
emailSettings={
|
||||
Object {
|
||||
{
|
||||
"hide": [MockFunction emailSettingHide],
|
||||
"isVisible": false,
|
||||
"show": [MockFunction emailSettingShow],
|
||||
@@ -58,7 +58,7 @@ exports[`CourseCardMenu render show dropdown show unenroll and enable email snap
|
||||
<SocialShareMenu
|
||||
cardId="test-card-id"
|
||||
emailSettings={
|
||||
Object {
|
||||
{
|
||||
"hide": [MockFunction emailSettingHide],
|
||||
"isVisible": false,
|
||||
"show": [MockFunction emailSettingShow],
|
||||
|
||||
@@ -5,8 +5,8 @@ exports[`CourseCardImage snapshot renders clickable link course Image 1`] = `
|
||||
className="pgn__card-wrapper-image-cap overflow-visible orientation"
|
||||
href="home-url"
|
||||
onClick={
|
||||
Object {
|
||||
"trackCourseEvent": Object {
|
||||
{
|
||||
"trackCourseEvent": {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.courseImageClicked],
|
||||
"upgradeUrl": "home-url",
|
||||
|
||||
@@ -7,8 +7,8 @@ exports[`CourseCardTitle snapshot renders clickable link course title 1`] = `
|
||||
data-testid="CourseCardTitle"
|
||||
href="home-url"
|
||||
onClick={
|
||||
Object {
|
||||
"trackCourseEvent": Object {
|
||||
{
|
||||
"trackCourseEvent": {
|
||||
"cardId": "cardId",
|
||||
"eventName": [MockFunction segment.courseTitleClicked],
|
||||
"upgradeUrl": "home-url",
|
||||
|
||||
@@ -28,7 +28,7 @@ exports[`CourseFilterControls is not mobile snapshot 1`] = `
|
||||
>
|
||||
<FilterForm
|
||||
filters={
|
||||
Array [
|
||||
[
|
||||
"test-filter",
|
||||
]
|
||||
}
|
||||
@@ -84,7 +84,7 @@ exports[`CourseFilterControls mobile snapshot 1`] = `
|
||||
>
|
||||
<FilterForm
|
||||
filters={
|
||||
Array [
|
||||
[
|
||||
"test-filter",
|
||||
]
|
||||
}
|
||||
@@ -144,7 +144,7 @@ exports[`CourseFilterControls no courses snapshot 1`] = `
|
||||
>
|
||||
<FilterForm
|
||||
filters={
|
||||
Array [
|
||||
[
|
||||
"test-filter",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ exports[`FilterForm snapshot renders 1`] = `
|
||||
name="course-status-filters"
|
||||
onChange={[MockFunction handleFilterChange]}
|
||||
value={
|
||||
Array [
|
||||
[
|
||||
"test-filter",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -48,11 +48,11 @@ exports[`CoursesPanel with courses snapshot 1`] = `
|
||||
id="course_list"
|
||||
>
|
||||
<CourseList
|
||||
filterOptions={Object {}}
|
||||
filterOptions={{}}
|
||||
numPages={1}
|
||||
setPageNumber={[MockFunction setPageNumber]}
|
||||
showFilters={false}
|
||||
visibleList={Array []}
|
||||
visibleList={[]}
|
||||
/>
|
||||
</PluginSlot>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,8 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import { Container, Col, Row } from '@openedx/paragon';
|
||||
|
||||
import WidgetFooter from 'containers/WidgetContainers/WidgetFooter';
|
||||
import WidgetSidebar from '../WidgetContainers/WidgetSidebar';
|
||||
|
||||
import hooks from './hooks';
|
||||
|
||||
export const columnConfig = {
|
||||
@@ -23,11 +24,10 @@ export const columnConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
export const DashboardLayout = ({ children, sidebar: Sidebar }) => {
|
||||
export const DashboardLayout = ({ children }) => {
|
||||
const {
|
||||
isCollapsed,
|
||||
sidebarShowing,
|
||||
setSidebarShowing,
|
||||
} = hooks.useDashboardLayoutData();
|
||||
|
||||
const courseListColumnProps = sidebarShowing
|
||||
@@ -42,12 +42,7 @@ export const DashboardLayout = ({ children, sidebar: Sidebar }) => {
|
||||
</Col>
|
||||
<Col {...columnConfig.sidebar} className="sidebar-column">
|
||||
{!isCollapsed && (<h2 className="course-list-title"> </h2>)}
|
||||
<Sidebar setSidebarShowing={setSidebarShowing} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col>
|
||||
<WidgetFooter />
|
||||
<WidgetSidebar />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
@@ -55,7 +50,6 @@ export const DashboardLayout = ({ children, sidebar: Sidebar }) => {
|
||||
};
|
||||
DashboardLayout.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
sidebar: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default DashboardLayout;
|
||||
|
||||
@@ -16,17 +16,13 @@ const hookProps = {
|
||||
};
|
||||
hooks.useDashboardLayoutData.mockReturnValue(hookProps);
|
||||
|
||||
const props = {
|
||||
sidebar: jest.fn(() => 'test-sidebar-content'),
|
||||
};
|
||||
|
||||
const children = 'test-children';
|
||||
|
||||
let el;
|
||||
describe('DashboardLayout', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
el = shallow(<DashboardLayout {...props}>{children}</DashboardLayout>);
|
||||
el = shallow(<DashboardLayout>{children}</DashboardLayout>);
|
||||
});
|
||||
|
||||
const testColumns = () => {
|
||||
@@ -40,17 +36,13 @@ describe('DashboardLayout', () => {
|
||||
const columns = el.instance.findByType(Row)[0].findByType(Col);
|
||||
expect(columns[0].children).not.toHaveLength(0);
|
||||
});
|
||||
it('displays sidebar prop in second column', () => {
|
||||
it('displays WidgetSidebar in second column', () => {
|
||||
const columns = el.instance.findByType(Row)[0].findByType(Col);
|
||||
expect(columns[1].findByType(props.sidebar)).toHaveLength(1);
|
||||
});
|
||||
it('displays a footer in the second row', () => {
|
||||
const columns = el.instance.findByType(Row)[1].findByType(Col);
|
||||
expect(columns[0].children[0].type).toEqual('WidgetFooter');
|
||||
expect(columns[1].findByType('WidgetSidebar')).toHaveLength(1);
|
||||
});
|
||||
};
|
||||
const testSidebarLayout = () => {
|
||||
it('displays widthSidebar width for course list column', () => {
|
||||
it('displays withSidebar width for course list column', () => {
|
||||
const columns = el.instance.findByType(Row)[0].findByType(Col);
|
||||
Object.keys(columnConfig.courseList.withSidebar).forEach(size => {
|
||||
expect(columns[0].props[size]).toEqual(columnConfig.courseList.withSidebar[size]);
|
||||
|
||||
@@ -9,13 +9,13 @@ exports[`DashboardLayout collapsed sidebar not showing snapshot 1`] = `
|
||||
<Col
|
||||
className="course-list-column"
|
||||
lg={
|
||||
Object {
|
||||
{
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
}
|
||||
xl={
|
||||
Object {
|
||||
{
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
@@ -26,26 +26,19 @@ exports[`DashboardLayout collapsed sidebar not showing snapshot 1`] = `
|
||||
<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 />
|
||||
<WidgetSidebar />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
@@ -60,13 +53,13 @@ exports[`DashboardLayout collapsed sidebar showing snapshot 1`] = `
|
||||
<Col
|
||||
className="course-list-column"
|
||||
lg={
|
||||
Object {
|
||||
{
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
}
|
||||
xl={
|
||||
Object {
|
||||
{
|
||||
"offset": 0,
|
||||
"span": 8,
|
||||
}
|
||||
@@ -77,26 +70,19 @@ exports[`DashboardLayout collapsed sidebar showing snapshot 1`] = `
|
||||
<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 />
|
||||
<WidgetSidebar />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
@@ -111,13 +97,13 @@ exports[`DashboardLayout not collapsed sidebar not showing snapshot 1`] = `
|
||||
<Col
|
||||
className="course-list-column"
|
||||
lg={
|
||||
Object {
|
||||
{
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
}
|
||||
xl={
|
||||
Object {
|
||||
{
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
@@ -128,13 +114,13 @@ exports[`DashboardLayout not collapsed sidebar not showing snapshot 1`] = `
|
||||
<Col
|
||||
className="sidebar-column"
|
||||
lg={
|
||||
Object {
|
||||
{
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
}
|
||||
xl={
|
||||
Object {
|
||||
{
|
||||
"offset": 0,
|
||||
"span": 4,
|
||||
}
|
||||
@@ -145,14 +131,7 @@ exports[`DashboardLayout not collapsed sidebar not showing snapshot 1`] = `
|
||||
>
|
||||
|
||||
</h2>
|
||||
<mockConstructor
|
||||
setSidebarShowing={[MockFunction hooks.setSidebarShowing]}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col>
|
||||
<WidgetFooter />
|
||||
<WidgetSidebar />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
@@ -167,13 +146,13 @@ exports[`DashboardLayout not collapsed sidebar showing snapshot 1`] = `
|
||||
<Col
|
||||
className="course-list-column"
|
||||
lg={
|
||||
Object {
|
||||
{
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
}
|
||||
xl={
|
||||
Object {
|
||||
{
|
||||
"offset": 0,
|
||||
"span": 8,
|
||||
}
|
||||
@@ -184,13 +163,13 @@ exports[`DashboardLayout not collapsed sidebar showing snapshot 1`] = `
|
||||
<Col
|
||||
className="sidebar-column"
|
||||
lg={
|
||||
Object {
|
||||
{
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
}
|
||||
xl={
|
||||
Object {
|
||||
{
|
||||
"offset": 0,
|
||||
"span": 4,
|
||||
}
|
||||
@@ -201,14 +180,7 @@ exports[`DashboardLayout not collapsed sidebar showing snapshot 1`] = `
|
||||
>
|
||||
|
||||
</h2>
|
||||
<mockConstructor
|
||||
setSidebarShowing={[MockFunction hooks.setSidebarShowing]}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col>
|
||||
<WidgetFooter />
|
||||
<WidgetSidebar />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
|
||||
@@ -17,9 +17,7 @@ exports[`Dashboard snapshots courses loaded, show select session modal, no avail
|
||||
data-testid="dashboard-content"
|
||||
id="dashboard-content"
|
||||
>
|
||||
<DashboardLayout
|
||||
sidebar="LoadedWidgetSidebar"
|
||||
>
|
||||
<DashboardLayout>
|
||||
<CoursesPanel />
|
||||
</DashboardLayout>
|
||||
</div>
|
||||
@@ -62,9 +60,7 @@ exports[`Dashboard snapshots there are no courses, there ARE available dashboard
|
||||
data-testid="dashboard-content"
|
||||
id="dashboard-content"
|
||||
>
|
||||
<DashboardLayout
|
||||
sidebar="NoCoursesWidgetSidebar"
|
||||
>
|
||||
<DashboardLayout>
|
||||
<CoursesPanel />
|
||||
</DashboardLayout>
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,8 @@ export const useDashboardMessages = () => {
|
||||
|
||||
export const useDashboardLayoutData = () => {
|
||||
const { width } = useWindowSize();
|
||||
const [sidebarShowing, setSidebarShowing] = module.state.sidebarShowing(false);
|
||||
|
||||
const [sidebarShowing, setSidebarShowing] = module.state.sidebarShowing(true);
|
||||
return {
|
||||
isDashboardCollapsed: width < breakpoints.large.maxWidth,
|
||||
sidebarShowing,
|
||||
|
||||
@@ -40,9 +40,9 @@ describe('CourseCard hooks', () => {
|
||||
describe('useDashboardLayoutData', () => {
|
||||
beforeEach(() => { state.mock(); });
|
||||
describe('behavior', () => {
|
||||
it('initializes sidebarShowing to default false value', () => {
|
||||
it('initializes sidebarShowing to default true value', () => {
|
||||
hooks.useDashboardLayoutData();
|
||||
state.expectInitializedWith(state.keys.sidebarShowing, false);
|
||||
state.expectInitializedWith(state.keys.sidebarShowing, true);
|
||||
});
|
||||
});
|
||||
describe('output', () => {
|
||||
|
||||
@@ -6,9 +6,6 @@ import EnterpriseDashboardModal from 'containers/EnterpriseDashboardModal';
|
||||
import SelectSessionModal from 'containers/SelectSessionModal';
|
||||
import CoursesPanel from 'containers/CoursesPanel';
|
||||
|
||||
import LoadedSidebar from 'containers/WidgetContainers/LoadedSidebar';
|
||||
import NoCoursesSidebar from 'containers/WidgetContainers/NoCoursesSidebar';
|
||||
|
||||
import LoadingView from './LoadingView';
|
||||
import DashboardLayout from './DashboardLayout';
|
||||
import hooks from './hooks';
|
||||
@@ -35,7 +32,7 @@ export const Dashboard = () => {
|
||||
{initIsPending
|
||||
? (<LoadingView />)
|
||||
: (
|
||||
<DashboardLayout sidebar={hasCourses ? LoadedSidebar : NoCoursesSidebar}>
|
||||
<DashboardLayout>
|
||||
<CoursesPanel />
|
||||
</DashboardLayout>
|
||||
)}
|
||||
|
||||
@@ -6,9 +6,6 @@ import EnterpriseDashboardModal from 'containers/EnterpriseDashboardModal';
|
||||
import SelectSessionModal from 'containers/SelectSessionModal';
|
||||
import CoursesPanel from 'containers/CoursesPanel';
|
||||
|
||||
import LoadedWidgetSidebar from 'containers/WidgetContainers/LoadedSidebar';
|
||||
import NoCoursesWidgetSidebar from 'containers/WidgetContainers/NoCoursesSidebar';
|
||||
|
||||
import DashboardLayout from './DashboardLayout';
|
||||
import LoadingView from './LoadingView';
|
||||
import hooks from './hooks';
|
||||
@@ -25,8 +22,6 @@ jest.mock('hooks', () => ({
|
||||
|
||||
jest.mock('containers/EnterpriseDashboardModal', () => 'EnterpriseDashboardModal');
|
||||
jest.mock('containers/CoursesPanel', () => 'CoursesPanel');
|
||||
jest.mock('containers/WidgetContainers/LoadedSidebar', () => 'LoadedWidgetSidebar');
|
||||
jest.mock('containers/WidgetContainers/NoCoursesSidebar', () => 'NoCoursesWidgetSidebar');
|
||||
jest.mock('./LoadingView', () => 'LoadingView');
|
||||
jest.mock('./DashboardLayout', () => 'DashboardLayout');
|
||||
|
||||
@@ -116,7 +111,7 @@ describe('Dashboard', () => {
|
||||
showSelectSessionModal: true,
|
||||
},
|
||||
content: ['LoadedView', (
|
||||
<DashboardLayout sidebar={LoadedWidgetSidebar}><CoursesPanel /></DashboardLayout>
|
||||
<DashboardLayout><CoursesPanel /></DashboardLayout>
|
||||
)],
|
||||
showEnterpriseModal: false,
|
||||
showSelectSessionModal: true,
|
||||
@@ -132,7 +127,7 @@ describe('Dashboard', () => {
|
||||
showSelectSessionModal: false,
|
||||
},
|
||||
content: ['Dashboard layout with no courses sidebar and content', (
|
||||
<DashboardLayout sidebar={NoCoursesWidgetSidebar}><CoursesPanel /></DashboardLayout>
|
||||
<DashboardLayout><CoursesPanel /></DashboardLayout>
|
||||
)],
|
||||
showEnterpriseModal: true,
|
||||
showSelectSessionModal: false,
|
||||
|
||||
@@ -10,7 +10,7 @@ exports[`EmailSettingsModal render snapshot: emails disabled, show: false 1`] =
|
||||
<div
|
||||
className="bg-white p-3 rounded shadow"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"textAlign": "start",
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ exports[`EmailSettingsModal render snapshot: emails disabled, show: true 1`] = `
|
||||
<div
|
||||
className="bg-white p-3 rounded shadow"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"textAlign": "start",
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ exports[`EmailSettingsModal render snapshot: emails enabled, show: true 1`] = `
|
||||
<div
|
||||
className="bg-white p-3 rounded shadow"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"textAlign": "start",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ exports[`EnterpriseDashboard snapshot 1`] = `
|
||||
<div
|
||||
className="bg-white p-3 rounded shadow"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"textAlign": "start",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
@@ -10,7 +12,7 @@ export const BrandLogo = () => {
|
||||
const dashboard = reduxHooks.useEnterpriseDashboardData();
|
||||
|
||||
return (
|
||||
<a href={dashboard?.url || getConfig().LMS_BASE_URL} className="mx-auto">
|
||||
<a href={dashboard?.url || '/'} className="mx-auto">
|
||||
<img
|
||||
className="logo py-3"
|
||||
src={getConfig().LOGO_URL}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import BrandLogo from './BrandLogo';
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
@@ -24,6 +23,6 @@ describe('BrandLogo', () => {
|
||||
reduxHooks.useEnterpriseDashboardData.mockReturnValueOnce(null);
|
||||
const wrapper = shallow(<BrandLogo />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
expect(wrapper.instance.findByType('a')[0].props.href).toEqual(getConfig().LMS_BASE_URL);
|
||||
expect(wrapper.instance.findByType('a')[0].props.href).toEqual('/');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
import React from 'react';
|
||||
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 '@openedx/paragon';
|
||||
|
||||
import urls from 'data/services/lms/urls';
|
||||
import { reduxHooks, apiHooks } from 'hooks';
|
||||
|
||||
import { findCoursesNavDropdownClicked } from '../hooks';
|
||||
import messages from '../messages';
|
||||
|
||||
export const CollapseMenuBody = ({ isOpen }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { authenticatedUser } = React.useContext(AppContext);
|
||||
|
||||
const { enabled: programsEnabled } = apiHooks.useProgramsConfig();
|
||||
|
||||
const dashboard = reduxHooks.useEnterpriseDashboardData();
|
||||
const { courseSearchUrl } = reduxHooks.usePlatformSettingsData();
|
||||
|
||||
const exploreCoursesClick = findCoursesNavDropdownClicked(
|
||||
urls.baseAppUrl(courseSearchUrl),
|
||||
);
|
||||
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="d-flex flex-column shadow-sm nav-small-menu">
|
||||
<Button as="a" href={`${getConfig().LMS_BASE_URL}/dashboard/`} variant="inverse-primary">
|
||||
{formatMessage(messages.course)}
|
||||
</Button>
|
||||
{programsEnabled && (
|
||||
<Button as="a" href={urls.programsUrl()} variant="inverse-primary">
|
||||
{formatMessage(messages.program)}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
as="a"
|
||||
href={urls.baseAppUrl(courseSearchUrl)}
|
||||
variant="inverse-primary"
|
||||
onClick={exploreCoursesClick}
|
||||
>
|
||||
{formatMessage(messages.discoverNew)}
|
||||
</Button>
|
||||
<Button as="a" href={getConfig().SUPPORT_URL} variant="inverse-primary">
|
||||
{formatMessage(messages.help)}
|
||||
</Button>
|
||||
{authenticatedUser && (
|
||||
<>
|
||||
{!!dashboard && (
|
||||
<Button as="a" href={dashboard.url} variant="inverse-primary">
|
||||
{formatMessage(messages.dashboard)}
|
||||
</Button>
|
||||
)}
|
||||
{!dashboard && getConfig().CAREER_LINK_URL && (
|
||||
<Button href={`${getConfig().CAREER_LINK_URL}`}>
|
||||
{formatMessage(messages.career)}
|
||||
<Badge className="px-2 mx-2" variant="warning">
|
||||
{formatMessage(messages.newAlert)}
|
||||
</Badge>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
as="a"
|
||||
href={`${getConfig().LMS_BASE_URL}/u/${authenticatedUser.username}`}
|
||||
variant="inverse-primary"
|
||||
>
|
||||
{formatMessage(messages.profile)}
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href={`${getConfig().LMS_BASE_URL}/account/settings`}
|
||||
variant="inverse-primary"
|
||||
>
|
||||
{formatMessage(messages.account)}
|
||||
</Button>
|
||||
{getConfig().ORDER_HISTORY_URL && (
|
||||
<Button
|
||||
as="a"
|
||||
variant="inverse-primary"
|
||||
href={getConfig().ORDER_HISTORY_URL}
|
||||
>
|
||||
{formatMessage(messages.orderHistory)}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
as="a"
|
||||
href={getConfig().LOGOUT_URL}
|
||||
variant="inverse-primary"
|
||||
>
|
||||
{formatMessage(messages.signOut)}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
CollapseMenuBody.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default CollapseMenuBody;
|
||||
@@ -1,60 +0,0 @@
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
|
||||
import { apiHooks } from 'hooks';
|
||||
import CollapseMenuBody from './CollapseMenuBody';
|
||||
|
||||
jest.mock('@edx/frontend-platform/react', () => ({
|
||||
AppContext: {
|
||||
authenticatedUser: {
|
||||
username: 'username',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useEnterpriseDashboardData: () => ({
|
||||
url: 'url',
|
||||
}),
|
||||
usePlatformSettingsData: () => ({
|
||||
courseSearchUrl: '/courseSearchUrl',
|
||||
}),
|
||||
},
|
||||
apiHooks: {
|
||||
useProgramsConfig: () => ({
|
||||
enabled: true,
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../hooks', () => ({
|
||||
findCoursesNavDropdownClicked: (url) => jest.fn().mockName(`findCoursesNavDropdownClicked("${url}")`),
|
||||
}));
|
||||
|
||||
describe('CollapseMenuBody', () => {
|
||||
test('render', () => {
|
||||
const wrapper = shallow(<CollapseMenuBody isOpen />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render empty if not open', () => {
|
||||
const wrapper = shallow(<CollapseMenuBody isOpen={false} />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
expect(wrapper.isEmptyRender()).toBe(true);
|
||||
});
|
||||
|
||||
test('render unauthenticated', () => {
|
||||
const { authenticatedUser } = AppContext;
|
||||
AppContext.authenticatedUser = null;
|
||||
const wrapper = shallow(<CollapseMenuBody isOpen />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
AppContext.authenticatedUser = authenticatedUser;
|
||||
});
|
||||
|
||||
test('render with disabled programs', () => {
|
||||
apiHooks.useProgramsConfig = () => ({ enabled: false });
|
||||
const wrapper = shallow(<CollapseMenuBody isOpen />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,164 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CollapseMenuBody render 1`] = `
|
||||
<div
|
||||
className="d-flex flex-column shadow-sm nav-small-menu"
|
||||
>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/dashboard/"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Courses
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/dashboard/programs"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Programs
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/courseSearchUrl"
|
||||
onClick={[MockFunction findCoursesNavDropdownClicked("http://localhost:18000/courseSearchUrl")]}
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Discover New
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/support"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Help
|
||||
</Button>
|
||||
<Fragment>
|
||||
<Button
|
||||
as="a"
|
||||
href="url"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Dashboard
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/u/username"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Profile
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/account/settings"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Account
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/logout"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Sign Out
|
||||
</Button>
|
||||
</Fragment>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`CollapseMenuBody render empty if not open 1`] = `null`;
|
||||
|
||||
exports[`CollapseMenuBody render unauthenticated 1`] = `
|
||||
<div
|
||||
className="d-flex flex-column shadow-sm nav-small-menu"
|
||||
>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/dashboard/"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Courses
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/dashboard/programs"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Programs
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/courseSearchUrl"
|
||||
onClick={[MockFunction findCoursesNavDropdownClicked("http://localhost:18000/courseSearchUrl")]}
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Discover New
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/support"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Help
|
||||
</Button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`CollapseMenuBody render with disabled programs 1`] = `
|
||||
<div
|
||||
className="d-flex flex-column shadow-sm nav-small-menu"
|
||||
>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/dashboard/"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Courses
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/courseSearchUrl"
|
||||
onClick={[MockFunction findCoursesNavDropdownClicked("http://localhost:18000/courseSearchUrl")]}
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Discover New
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/support"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Help
|
||||
</Button>
|
||||
<Fragment>
|
||||
<Button
|
||||
as="a"
|
||||
href="url"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Dashboard
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/u/username"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Profile
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/account/settings"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Account
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href="http://localhost:18000/logout"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Sign Out
|
||||
</Button>
|
||||
</Fragment>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,48 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CollapsedHeader render nothing if not collapsed 1`] = `false`;
|
||||
|
||||
exports[`CollapsedHeader renders 1`] = `
|
||||
<Fragment>
|
||||
<header
|
||||
className="d-flex shadow-sm align-items-center learner-variant-header"
|
||||
>
|
||||
<IconButton
|
||||
alt="Close"
|
||||
className="p-4"
|
||||
iconAs="Icon"
|
||||
invertColors={true}
|
||||
isActive={true}
|
||||
onClick={[MockFunction toggleIsOpen]}
|
||||
variant="primary"
|
||||
/>
|
||||
<mockConstructor />
|
||||
</header>
|
||||
<mockConstructor
|
||||
isOpen={false}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`CollapsedHeader renders with isOpen true 1`] = `
|
||||
<Fragment>
|
||||
<header
|
||||
className="d-flex shadow-sm align-items-center learner-variant-header"
|
||||
>
|
||||
<IconButton
|
||||
alt="Menu"
|
||||
className="p-4"
|
||||
iconAs="Icon"
|
||||
invertColors={true}
|
||||
isActive={true}
|
||||
onClick={[MockFunction toggleIsOpen]}
|
||||
src={[MockFunction icons.Close]}
|
||||
variant="primary"
|
||||
/>
|
||||
<mockConstructor />
|
||||
</header>
|
||||
<mockConstructor
|
||||
isOpen={true}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
@@ -1,47 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { MenuIcon, Close } from '@openedx/paragon/icons';
|
||||
import { IconButton, Icon } from '@openedx/paragon';
|
||||
|
||||
import { useLearnerDashboardHeaderData, useIsCollapsed } from '../hooks';
|
||||
|
||||
import CollapseMenuBody from './CollapseMenuBody';
|
||||
import BrandLogo from '../BrandLogo';
|
||||
|
||||
import messages from '../messages';
|
||||
|
||||
export const CollapsedHeader = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const isCollapsed = useIsCollapsed();
|
||||
const { isOpen, toggleIsOpen } = useLearnerDashboardHeaderData();
|
||||
|
||||
return (
|
||||
isCollapsed && (
|
||||
<>
|
||||
<header className="d-flex shadow-sm align-items-center learner-variant-header">
|
||||
<IconButton
|
||||
invertColors
|
||||
isActive
|
||||
src={isOpen ? Close : MenuIcon}
|
||||
iconAs={Icon}
|
||||
alt={
|
||||
isOpen
|
||||
? formatMessage(messages.collapseMenuOpenAltText)
|
||||
: formatMessage(messages.collapseMenuClosedAltText)
|
||||
}
|
||||
onClick={toggleIsOpen}
|
||||
variant="primary"
|
||||
className="p-4"
|
||||
/>
|
||||
<BrandLogo />
|
||||
</header>
|
||||
<CollapseMenuBody isOpen={isOpen} />
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
CollapsedHeader.propTypes = {};
|
||||
|
||||
export default CollapsedHeader;
|
||||
@@ -1,38 +0,0 @@
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import CollapsedHeader from '.';
|
||||
|
||||
import { useLearnerDashboardHeaderData, useIsCollapsed } from '../hooks';
|
||||
|
||||
jest.mock('../BrandLogo', () => jest.fn(() => 'BrandLogo'));
|
||||
jest.mock('./CollapseMenuBody', () => jest.fn(() => 'CollapseMenuBody'));
|
||||
|
||||
jest.mock('../hooks', () => ({
|
||||
useIsCollapsed: jest.fn(() => true),
|
||||
useLearnerDashboardHeaderData: jest.fn(() => ({
|
||||
isOpen: false,
|
||||
toggleIsOpen: jest.fn().mockName('toggleIsOpen'),
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('CollapsedHeader', () => {
|
||||
it('renders', () => {
|
||||
const wrapper = shallow(<CollapsedHeader />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('render nothing if not collapsed', () => {
|
||||
useIsCollapsed.mockReturnValueOnce(false);
|
||||
const wrapper = shallow(<CollapsedHeader />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders with isOpen true', () => {
|
||||
useLearnerDashboardHeaderData.mockReturnValueOnce({
|
||||
isOpen: true,
|
||||
toggleIsOpen: jest.fn().mockName('toggleIsOpen'),
|
||||
});
|
||||
const wrapper = shallow(<CollapsedHeader />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -9,14 +9,14 @@ exports[`ConfirmEmailBanner snapshot Show on unverified 1`] = `
|
||||
>
|
||||
<format-message-function
|
||||
message={
|
||||
Object {
|
||||
{
|
||||
"defaultMessage": "Remember to confirm your email so that you can keep learning! {confirmNowButton}.",
|
||||
"description": "Text for reminding user to confirm email",
|
||||
"id": "leanerDashboard.confirmEmailTextReminderBanner",
|
||||
}
|
||||
}
|
||||
values={
|
||||
Object {
|
||||
{
|
||||
"confirmNowButton": <Button
|
||||
className="confirm-email-now-button"
|
||||
onClick={[MockFunction openConfirmModalButtonClick]}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
import { AvatarButton, Dropdown, Badge } from '@openedx/paragon';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import messages from '../messages';
|
||||
|
||||
export const AuthenticatedUserDropdown = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { authenticatedUser } = React.useContext(AppContext);
|
||||
const dashboard = reduxHooks.useEnterpriseDashboardData();
|
||||
|
||||
return (
|
||||
authenticatedUser && (
|
||||
<Dropdown className="user-dropdown pr4">
|
||||
<Dropdown.Toggle
|
||||
as={AvatarButton}
|
||||
src={authenticatedUser.profileImage}
|
||||
id="user"
|
||||
variant="light"
|
||||
className="p-4"
|
||||
>
|
||||
<span data-hj-suppress className="d-md-inline">
|
||||
{authenticatedUser.username}
|
||||
</span>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="dropdown-menu-right">
|
||||
{ getConfig().ENABLE_EDX_PERSONAL_DASHBOARD && (
|
||||
<>
|
||||
<Dropdown.Header>{formatMessage(messages.dashboardSwitch)}</Dropdown.Header>
|
||||
<Dropdown.Item as="a" href="/edx-dashboard" className="active">
|
||||
{formatMessage(messages.dashboardPersonal)}
|
||||
</Dropdown.Item>
|
||||
{!!dashboard && (
|
||||
<Dropdown.Item as="a" href={dashboard.url} key={dashboard.label}>
|
||||
{dashboard.label} {formatMessage(messages.dashboard)}
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
<Dropdown.Divider />
|
||||
</>
|
||||
)}
|
||||
|
||||
{!dashboard && getConfig().CAREER_LINK_URL && (
|
||||
<Dropdown.Item href={`${getConfig().CAREER_LINK_URL}`}>
|
||||
{formatMessage(messages.career)}
|
||||
<Badge className="px-2 mx-2" variant="warning">
|
||||
{formatMessage(messages.newAlert)}
|
||||
</Badge>
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
<Dropdown.Item href={`${getConfig().ACCOUNT_PROFILE_URL}/u/${authenticatedUser.username}`}>
|
||||
{formatMessage(messages.profile)}
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item href={getConfig().ACCOUNT_SETTINGS_URL}>
|
||||
{formatMessage(messages.account)}
|
||||
</Dropdown.Item>
|
||||
{getConfig().ORDER_HISTORY_URL && (
|
||||
<Dropdown.Item href={getConfig().ORDER_HISTORY_URL}>
|
||||
{formatMessage(messages.orderHistory)}
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
<Dropdown.Divider />
|
||||
<Dropdown.Item href={getConfig().LOGOUT_URL}>
|
||||
{formatMessage(messages.signOut)}
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
AuthenticatedUserDropdown.propTypes = {};
|
||||
|
||||
export default AuthenticatedUserDropdown;
|
||||
@@ -1,81 +0,0 @@
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
import { AuthenticatedUserDropdown } from './AuthenticatedUserDropdown';
|
||||
import { useIsCollapsed } from '../hooks';
|
||||
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
getConfig: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-platform/react', () => ({
|
||||
AppContext: {
|
||||
authenticatedUser: {
|
||||
profileImage: 'profileImage',
|
||||
username: 'username',
|
||||
},
|
||||
},
|
||||
}));
|
||||
const COURSE_SEARCH_URL = 'test-course-search-url';
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useEnterpriseDashboardData: jest.fn(),
|
||||
usePlatformSettingsData: jest.fn(() => ({
|
||||
courseSearchUrl: COURSE_SEARCH_URL,
|
||||
})),
|
||||
},
|
||||
}));
|
||||
jest.mock('../hooks', () => ({
|
||||
useIsCollapsed: jest.fn(),
|
||||
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',
|
||||
LOGOUT_URL: 'http://logout-url.test',
|
||||
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',
|
||||
ENABLE_EDX_PERSONAL_DASHBOARD: true,
|
||||
};
|
||||
getConfig.mockReturnValue(config);
|
||||
|
||||
describe('AuthenticatedUserDropdown', () => {
|
||||
const defaultDashboardData = {
|
||||
label: 'label',
|
||||
url: 'url',
|
||||
};
|
||||
|
||||
describe('snapshots', () => {
|
||||
test('no auth render empty', () => {
|
||||
const { authenticatedUser } = AppContext;
|
||||
AppContext.authenticatedUser = null;
|
||||
const wrapper = shallow(<AuthenticatedUserDropdown />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
expect(wrapper.isEmptyRender()).toBe(true);
|
||||
AppContext.authenticatedUser = authenticatedUser;
|
||||
});
|
||||
test('with enterprise dashboard', () => {
|
||||
reduxHooks.useEnterpriseDashboardData.mockReturnValueOnce(defaultDashboardData);
|
||||
useIsCollapsed.mockReturnValueOnce(true);
|
||||
const wrapper = shallow(<AuthenticatedUserDropdown />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
test('without enterprise dashboard and expanded', () => {
|
||||
reduxHooks.useEnterpriseDashboardData.mockReturnValueOnce(null);
|
||||
useIsCollapsed.mockReturnValueOnce(false);
|
||||
const wrapper = shallow(<AuthenticatedUserDropdown />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,139 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AuthenticatedUserDropdown snapshots no auth render empty 1`] = `null`;
|
||||
|
||||
exports[`AuthenticatedUserDropdown snapshots with enterprise dashboard 1`] = `
|
||||
<Dropdown
|
||||
className="user-dropdown pr4"
|
||||
>
|
||||
<Dropdown.Toggle
|
||||
className="p-4"
|
||||
id="user"
|
||||
src="profileImage"
|
||||
variant="light"
|
||||
>
|
||||
<span
|
||||
className="d-md-inline"
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
username
|
||||
</span>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu
|
||||
className="dropdown-menu-right"
|
||||
>
|
||||
<Fragment>
|
||||
<Dropdown.Header>
|
||||
SWITCH DASHBOARD
|
||||
</Dropdown.Header>
|
||||
<Dropdown.Item
|
||||
as="a"
|
||||
className="active"
|
||||
href="/edx-dashboard"
|
||||
>
|
||||
Personal
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
as="a"
|
||||
href="url"
|
||||
key="label"
|
||||
>
|
||||
label
|
||||
|
||||
Dashboard
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Divider />
|
||||
</Fragment>
|
||||
<Dropdown.Item
|
||||
href="http://account-profile-url.test/u/username"
|
||||
>
|
||||
Profile
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
href="http://account-settings-url.test"
|
||||
>
|
||||
Account
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
href="http://order-history-url.test"
|
||||
>
|
||||
Order History
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Divider />
|
||||
<Dropdown.Item
|
||||
href="http://logout-url.test"
|
||||
>
|
||||
Sign Out
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
`;
|
||||
|
||||
exports[`AuthenticatedUserDropdown snapshots without enterprise dashboard and expanded 1`] = `
|
||||
<Dropdown
|
||||
className="user-dropdown pr4"
|
||||
>
|
||||
<Dropdown.Toggle
|
||||
className="p-4"
|
||||
id="user"
|
||||
src="profileImage"
|
||||
variant="light"
|
||||
>
|
||||
<span
|
||||
className="d-md-inline"
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
username
|
||||
</span>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu
|
||||
className="dropdown-menu-right"
|
||||
>
|
||||
<Fragment>
|
||||
<Dropdown.Header>
|
||||
SWITCH DASHBOARD
|
||||
</Dropdown.Header>
|
||||
<Dropdown.Item
|
||||
as="a"
|
||||
className="active"
|
||||
href="/edx-dashboard"
|
||||
>
|
||||
Personal
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Divider />
|
||||
</Fragment>
|
||||
<Dropdown.Item
|
||||
href="http://localhost:18000/career"
|
||||
>
|
||||
Career
|
||||
<Badge
|
||||
className="px-2 mx-2"
|
||||
variant="warning"
|
||||
>
|
||||
New
|
||||
</Badge>
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
href="http://account-profile-url.test/u/username"
|
||||
>
|
||||
Profile
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
href="http://account-settings-url.test"
|
||||
>
|
||||
Account
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
href="http://order-history-url.test"
|
||||
>
|
||||
Order History
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Divider />
|
||||
<Dropdown.Item
|
||||
href="http://logout-url.test"
|
||||
>
|
||||
Sign Out
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
`;
|
||||
@@ -1,93 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ExpandedHeader render 1`] = `
|
||||
<header
|
||||
className="d-flex shadow-sm align-items-center learner-variant-header pl-4"
|
||||
>
|
||||
<div
|
||||
className="flex-grow-1 d-flex align-items-center"
|
||||
>
|
||||
<BrandLogo />
|
||||
<Button
|
||||
as="a"
|
||||
className="p-4 course-link"
|
||||
href="http://localhost:18000/dashboard/"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Courses
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
className="p-4"
|
||||
href="programsUrl"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Programs
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
className="p-4"
|
||||
href="http://localhost:18000/courseSearchUrl"
|
||||
onClick={[MockFunction findCoursesNavClicked("http://localhost:18000/courseSearchUrl")]}
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Discover New
|
||||
</Button>
|
||||
<span
|
||||
className="flex-grow-1"
|
||||
/>
|
||||
<Button
|
||||
as="a"
|
||||
className="p-4"
|
||||
href="http://localhost:18000/support"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Help
|
||||
</Button>
|
||||
</div>
|
||||
<AuthenticatedUserDropdown />
|
||||
</header>
|
||||
`;
|
||||
|
||||
exports[`ExpandedHeader render empty if collapsed 1`] = `null`;
|
||||
|
||||
exports[`ExpandedHeader render with disabled programs 1`] = `
|
||||
<header
|
||||
className="d-flex shadow-sm align-items-center learner-variant-header pl-4"
|
||||
>
|
||||
<div
|
||||
className="flex-grow-1 d-flex align-items-center"
|
||||
>
|
||||
<BrandLogo />
|
||||
<Button
|
||||
as="a"
|
||||
className="p-4 course-link"
|
||||
href="http://localhost:18000/dashboard/"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Courses
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
className="p-4"
|
||||
href="http://localhost:18000/courseSearchUrl"
|
||||
onClick={[MockFunction findCoursesNavClicked("http://localhost:18000/courseSearchUrl")]}
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Discover New
|
||||
</Button>
|
||||
<span
|
||||
className="flex-grow-1"
|
||||
/>
|
||||
<Button
|
||||
as="a"
|
||||
className="p-4"
|
||||
href="http://localhost:18000/support"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Help
|
||||
</Button>
|
||||
</div>
|
||||
<AuthenticatedUserDropdown />
|
||||
</header>
|
||||
`;
|
||||
@@ -1,80 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Button } from '@openedx/paragon';
|
||||
|
||||
import urls from 'data/services/lms/urls';
|
||||
import { reduxHooks, apiHooks } from 'hooks';
|
||||
|
||||
import AuthenticatedUserDropdown from './AuthenticatedUserDropdown';
|
||||
import { useIsCollapsed, findCoursesNavClicked } from '../hooks';
|
||||
import messages from '../messages';
|
||||
import BrandLogo from '../BrandLogo';
|
||||
|
||||
export const ExpandedHeader = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { courseSearchUrl } = reduxHooks.usePlatformSettingsData();
|
||||
const isCollapsed = useIsCollapsed();
|
||||
|
||||
const { enabled: programsEnabled } = apiHooks.useProgramsConfig();
|
||||
|
||||
const exploreCoursesClick = findCoursesNavClicked(
|
||||
urls.baseAppUrl(courseSearchUrl),
|
||||
);
|
||||
|
||||
if (isCollapsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="d-flex shadow-sm align-items-center learner-variant-header pl-4">
|
||||
<div className="flex-grow-1 d-flex align-items-center">
|
||||
<BrandLogo />
|
||||
|
||||
<Button
|
||||
as="a"
|
||||
href={`${getConfig().LMS_BASE_URL}/dashboard/`}
|
||||
variant="inverse-primary"
|
||||
className="p-4 course-link"
|
||||
>
|
||||
{formatMessage(messages.course)}
|
||||
</Button>
|
||||
{programsEnabled && (
|
||||
<Button
|
||||
as="a"
|
||||
href={urls.programsUrl()}
|
||||
variant="inverse-primary"
|
||||
className="p-4"
|
||||
>
|
||||
{formatMessage(messages.program)}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
as="a"
|
||||
href={urls.baseAppUrl(courseSearchUrl)}
|
||||
variant="inverse-primary"
|
||||
className="p-4"
|
||||
onClick={exploreCoursesClick}
|
||||
>
|
||||
{formatMessage(messages.discoverNew)}
|
||||
</Button>
|
||||
<span className="flex-grow-1" />
|
||||
<Button
|
||||
as="a"
|
||||
href={getConfig().SUPPORT_URL}
|
||||
variant="inverse-primary"
|
||||
className="p-4"
|
||||
>
|
||||
{formatMessage(messages.help)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<AuthenticatedUserDropdown />
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
ExpandedHeader.propTypes = {};
|
||||
|
||||
export default ExpandedHeader;
|
||||
@@ -1,53 +0,0 @@
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { apiHooks } from 'hooks';
|
||||
import ExpandedHeader from '.';
|
||||
|
||||
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',
|
||||
}),
|
||||
},
|
||||
apiHooks: {
|
||||
useProgramsConfig: () => ({
|
||||
enabled: true,
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../hooks', () => ({
|
||||
useIsCollapsed: jest.fn(),
|
||||
findCoursesNavClicked: (url) => jest.fn().mockName(`findCoursesNavClicked("${url}")`),
|
||||
}));
|
||||
|
||||
jest.mock('./AuthenticatedUserDropdown', () => 'AuthenticatedUserDropdown');
|
||||
jest.mock('../BrandLogo', () => 'BrandLogo');
|
||||
|
||||
describe('ExpandedHeader', () => {
|
||||
test('render', () => {
|
||||
useIsCollapsed.mockReturnValueOnce(false);
|
||||
const wrapper = shallow(<ExpandedHeader />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render empty if collapsed', () => {
|
||||
useIsCollapsed.mockReturnValueOnce(true);
|
||||
const wrapper = shallow(<ExpandedHeader />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
expect(wrapper.isEmptyRender()).toBe(true);
|
||||
});
|
||||
|
||||
test('render with disabled programs', () => {
|
||||
apiHooks.useProgramsConfig = () => ({ enabled: false });
|
||||
const wrapper = shallow(<ExpandedHeader />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
|
||||
import urls from 'data/services/lms/urls';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const getLearnerHeaderMenu = (
|
||||
formatMessage,
|
||||
courseSearchUrl,
|
||||
authenticatedUser,
|
||||
exploreCoursesClick,
|
||||
) => ({
|
||||
mainMenu: [
|
||||
{
|
||||
type: 'item',
|
||||
href: '/',
|
||||
content: formatMessage(messages.course),
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: `${urls.programsUrl()}`,
|
||||
content: formatMessage(messages.program),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: `${urls.baseAppUrl(courseSearchUrl)}`,
|
||||
content: formatMessage(messages.discoverNew),
|
||||
onClick: (e) => {
|
||||
exploreCoursesClick(e);
|
||||
},
|
||||
},
|
||||
],
|
||||
secondaryMenu: [
|
||||
{
|
||||
type: 'item',
|
||||
href: `${getConfig().SUPPORT_URL}`,
|
||||
content: formatMessage(messages.help),
|
||||
},
|
||||
],
|
||||
userMenu: [
|
||||
{
|
||||
heading: '',
|
||||
items: [
|
||||
{
|
||||
type: 'item',
|
||||
href: `${getConfig().ACCOUNT_PROFILE_URL}/u/${authenticatedUser?.username}`,
|
||||
content: formatMessage(messages.profile),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: `${getConfig().ACCOUNT_SETTINGS_URL}`,
|
||||
content: formatMessage(messages.account),
|
||||
},
|
||||
...(getConfig().ORDER_HISTORY_URL ? [{
|
||||
type: 'item',
|
||||
href: getConfig().ORDER_HISTORY_URL,
|
||||
content: formatMessage(messages.orderHistory),
|
||||
}] : []),
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: '',
|
||||
items: [
|
||||
{
|
||||
type: 'item',
|
||||
href: `${getConfig().LOGOUT_URL}`,
|
||||
content: formatMessage(messages.signOut),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export default getLearnerHeaderMenu;
|
||||
@@ -16,7 +16,7 @@ exports[`BrandLogo dashboard defined 1`] = `
|
||||
exports[`BrandLogo dashboard undefined 1`] = `
|
||||
<a
|
||||
className="mx-auto"
|
||||
href="http://localhost:18000"
|
||||
href="/"
|
||||
>
|
||||
<img
|
||||
alt="edX, Inc. Dashboard"
|
||||
|
||||
@@ -3,8 +3,72 @@
|
||||
exports[`LearnerDashboardHeader render 1`] = `
|
||||
<Fragment>
|
||||
<ConfirmEmailBanner />
|
||||
<CollapsedHeader />
|
||||
<ExpandedHeader />
|
||||
<Header
|
||||
mainMenuItems={
|
||||
[
|
||||
{
|
||||
"content": "Courses",
|
||||
"href": "/",
|
||||
"isActive": true,
|
||||
"type": "item",
|
||||
},
|
||||
{
|
||||
"content": "Programs",
|
||||
"href": "http://localhost:18000/dashboard/programs",
|
||||
"type": "item",
|
||||
},
|
||||
{
|
||||
"content": "Discover New",
|
||||
"href": "http://localhost:18000/course-search-url",
|
||||
"onClick": [Function],
|
||||
"type": "item",
|
||||
},
|
||||
]
|
||||
}
|
||||
secondaryMenuItems={
|
||||
[
|
||||
{
|
||||
"content": "Help",
|
||||
"href": "http://localhost:18000/support",
|
||||
"type": "item",
|
||||
},
|
||||
]
|
||||
}
|
||||
userMenuItems={
|
||||
[
|
||||
{
|
||||
"heading": "",
|
||||
"items": [
|
||||
{
|
||||
"content": "Profile",
|
||||
"href": "http://account-profile-url.test/u/undefined",
|
||||
"type": "item",
|
||||
},
|
||||
{
|
||||
"content": "Account",
|
||||
"href": "http://account-settings-url.test",
|
||||
"type": "item",
|
||||
},
|
||||
{
|
||||
"content": "Order History",
|
||||
"href": "test-url",
|
||||
"type": "item",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"heading": "",
|
||||
"items": [
|
||||
{
|
||||
"content": "Sign Out",
|
||||
"href": "http://localhost:18000/logout",
|
||||
"type": "item",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
<MasqueradeBar />
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import React from 'react';
|
||||
import { useWindowSize, breakpoints } from '@openedx/paragon';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import track from 'tracking';
|
||||
import { StrictDict } from 'utils';
|
||||
import { linkNames } from 'tracking/constants';
|
||||
|
||||
import getLearnerHeaderMenu from './LearnerDashboardMenu';
|
||||
|
||||
import * as module from './hooks';
|
||||
|
||||
export const state = StrictDict({
|
||||
@@ -24,6 +27,13 @@ export const findCoursesNavDropdownClicked = (href) => track.findCourses.findCou
|
||||
linkName: linkNames.learnerHomeNavDropdownExplore,
|
||||
});
|
||||
|
||||
export const useLearnerDashboardHeaderMenu = ({
|
||||
courseSearchUrl, authenticatedUser, exploreCoursesClick,
|
||||
}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
return getLearnerHeaderMenu(formatMessage, courseSearchUrl, authenticatedUser, exploreCoursesClick);
|
||||
};
|
||||
|
||||
export const useLearnerDashboardHeaderData = () => {
|
||||
const [isOpen, setIsOpen] = module.state.isOpen(false);
|
||||
const toggleIsOpen = () => setIsOpen(!isOpen);
|
||||
@@ -39,4 +49,5 @@ export default {
|
||||
findCoursesNavClicked,
|
||||
findCoursesNavDropdownClicked,
|
||||
useLearnerDashboardHeaderData,
|
||||
useLearnerDashboardHeaderMenu,
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ const {
|
||||
findCoursesNavClicked,
|
||||
findCoursesNavDropdownClicked,
|
||||
useLearnerDashboardHeaderData,
|
||||
useLearnerDashboardHeaderMenu,
|
||||
} = hooks;
|
||||
|
||||
jest.mock('tracking', () => ({
|
||||
@@ -48,6 +49,17 @@ describe('LearnerDashboardHeader hooks', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLearnerDashboardHeaderMenu', () => {
|
||||
test('calls header menu data hook', () => {
|
||||
const courseSearchUrl = '/courses';
|
||||
const authenticatedUser = {
|
||||
username: 'test',
|
||||
};
|
||||
const learnerHomeHeaderMenu = useLearnerDashboardHeaderMenu({ courseSearchUrl, authenticatedUser });
|
||||
expect(learnerHomeHeaderMenu.mainMenu.length).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findCoursesNavDropdownClicked', () => {
|
||||
test('calls tracking with dropdown link name', () => {
|
||||
findCoursesNavDropdownClicked(url);
|
||||
|
||||
@@ -1,21 +1,43 @@
|
||||
import React from 'react';
|
||||
|
||||
import MasqueradeBar from 'containers/MasqueradeBar';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
import Header from '@edx/frontend-component-header';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import urls from 'data/services/lms/urls';
|
||||
|
||||
import ConfirmEmailBanner from './ConfirmEmailBanner';
|
||||
|
||||
import CollapsedHeader from './CollapsedHeader';
|
||||
import ExpandedHeader from './ExpandedHeader';
|
||||
import { useLearnerDashboardHeaderMenu, findCoursesNavClicked } from './hooks';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
export const LearnerDashboardHeader = () => (
|
||||
<>
|
||||
<ConfirmEmailBanner />
|
||||
<CollapsedHeader />
|
||||
<ExpandedHeader />
|
||||
<MasqueradeBar />
|
||||
</>
|
||||
);
|
||||
export const LearnerDashboardHeader = () => {
|
||||
const { authenticatedUser } = React.useContext(AppContext);
|
||||
const { courseSearchUrl } = reduxHooks.usePlatformSettingsData();
|
||||
|
||||
const exploreCoursesClick = () => {
|
||||
findCoursesNavClicked(urls.baseAppUrl(courseSearchUrl));
|
||||
};
|
||||
|
||||
const learnerHomeHeaderMenu = useLearnerDashboardHeaderMenu({
|
||||
courseSearchUrl,
|
||||
authenticatedUser,
|
||||
exploreCoursesClick,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConfirmEmailBanner />
|
||||
<Header
|
||||
mainMenuItems={learnerHomeHeaderMenu.mainMenu}
|
||||
secondaryMenuItems={learnerHomeHeaderMenu.secondaryMenu}
|
||||
userMenuItems={learnerHomeHeaderMenu.userMenu}
|
||||
/>
|
||||
<MasqueradeBar />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
LearnerDashboardHeader.propTypes = {};
|
||||
|
||||
|
||||
@@ -1,18 +1,35 @@
|
||||
import { mergeConfig } from '@edx/frontend-platform';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
import LearnerDashboardHeader from '.';
|
||||
import Header from '@edx/frontend-component-header';
|
||||
|
||||
import urls from 'data/services/lms/urls';
|
||||
import LearnerDashboardHeader from '.';
|
||||
import { findCoursesNavClicked } from './hooks';
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
usePlatformSettingsData: jest.fn(() => ({
|
||||
courseSearchUrl: '/course-search-url',
|
||||
})),
|
||||
},
|
||||
}));
|
||||
jest.mock('./hooks', () => ({
|
||||
...jest.requireActual('./hooks'),
|
||||
findCoursesNavClicked: jest.fn(),
|
||||
}));
|
||||
jest.mock('containers/MasqueradeBar', () => 'MasqueradeBar');
|
||||
jest.mock('./CollapsedHeader', () => 'CollapsedHeader');
|
||||
jest.mock('./ConfirmEmailBanner', () => 'ConfirmEmailBanner');
|
||||
jest.mock('./ExpandedHeader', () => 'ExpandedHeader');
|
||||
jest.mock('@edx/frontend-component-header', () => 'Header');
|
||||
|
||||
describe('LearnerDashboardHeader', () => {
|
||||
test('render', () => {
|
||||
mergeConfig({ ORDER_HISTORY_URL: 'test-url' });
|
||||
const wrapper = shallow(<LearnerDashboardHeader />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
expect(wrapper.instance.findByType('ConfirmEmailBanner')).toHaveLength(1);
|
||||
expect(wrapper.instance.findByType('MasqueradeBar')).toHaveLength(1);
|
||||
expect(wrapper.instance.findByType('CollapsedHeader')).toHaveLength(1);
|
||||
expect(wrapper.instance.findByType('ExpandedHeader')).toHaveLength(1);
|
||||
expect(wrapper.instance.findByType(Header)).toHaveLength(1);
|
||||
wrapper.instance.findByType(Header)[0].props.mainMenuItems[2].onClick();
|
||||
expect(findCoursesNavClicked).toHaveBeenCalledWith(urls.baseAppUrl('/course-search-url'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,7 +33,7 @@ exports[`MasqueradeBar snapshot can masquerade 1`] = `
|
||||
className="mr-3"
|
||||
disabled={true}
|
||||
labels={
|
||||
Object {
|
||||
{
|
||||
"default": "Submit",
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ exports[`MasqueradeBar snapshot can masquerade with input 1`] = `
|
||||
className="mr-3"
|
||||
disabled={false}
|
||||
labels={
|
||||
Object {
|
||||
{
|
||||
"default": "Submit",
|
||||
}
|
||||
}
|
||||
@@ -131,7 +131,7 @@ exports[`MasqueradeBar snapshot is masquerading failed with error 1`] = `
|
||||
className="mr-3"
|
||||
disabled={true}
|
||||
labels={
|
||||
Object {
|
||||
{
|
||||
"default": "Submit",
|
||||
}
|
||||
}
|
||||
@@ -177,7 +177,7 @@ exports[`MasqueradeBar snapshot is masquerading pending 1`] = `
|
||||
className="mr-3"
|
||||
disabled={true}
|
||||
labels={
|
||||
Object {
|
||||
{
|
||||
"default": "Submit",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ exports[`RelatedProgramsModal snapshot: closed 1`] = `
|
||||
>
|
||||
<ProgramCard
|
||||
data={
|
||||
Object {
|
||||
"programData": Object {
|
||||
{
|
||||
"programData": {
|
||||
"dataFor": "program1",
|
||||
},
|
||||
"programUrl": "program-1-url",
|
||||
@@ -53,8 +53,8 @@ exports[`RelatedProgramsModal snapshot: closed 1`] = `
|
||||
>
|
||||
<ProgramCard
|
||||
data={
|
||||
Object {
|
||||
"programData": Object {
|
||||
{
|
||||
"programData": {
|
||||
"dataFor": "program2",
|
||||
},
|
||||
"programUrl": "program-2-url",
|
||||
@@ -69,8 +69,8 @@ exports[`RelatedProgramsModal snapshot: closed 1`] = `
|
||||
>
|
||||
<ProgramCard
|
||||
data={
|
||||
Object {
|
||||
"programData": Object {
|
||||
{
|
||||
"programData": {
|
||||
"dataFor": "program3",
|
||||
},
|
||||
"programUrl": "program-3-url",
|
||||
@@ -121,8 +121,8 @@ exports[`RelatedProgramsModal snapshot: open 1`] = `
|
||||
>
|
||||
<ProgramCard
|
||||
data={
|
||||
Object {
|
||||
"programData": Object {
|
||||
{
|
||||
"programData": {
|
||||
"dataFor": "program1",
|
||||
},
|
||||
"programUrl": "program-1-url",
|
||||
@@ -137,8 +137,8 @@ exports[`RelatedProgramsModal snapshot: open 1`] = `
|
||||
>
|
||||
<ProgramCard
|
||||
data={
|
||||
Object {
|
||||
"programData": Object {
|
||||
{
|
||||
"programData": {
|
||||
"dataFor": "program2",
|
||||
},
|
||||
"programUrl": "program-2-url",
|
||||
@@ -153,8 +153,8 @@ exports[`RelatedProgramsModal snapshot: open 1`] = `
|
||||
>
|
||||
<ProgramCard
|
||||
data={
|
||||
Object {
|
||||
"programData": Object {
|
||||
{
|
||||
"programData": {
|
||||
"dataFor": "program3",
|
||||
},
|
||||
"programUrl": "program-3-url",
|
||||
|
||||
@@ -7,7 +7,7 @@ exports[`RelatedProgramsModal ProgramCard snapshot 1`] = `
|
||||
href="props.data.programUrl"
|
||||
isClickable={true}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "white",
|
||||
"width": "18rem",
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ exports[`UnenrollConfirmModal component snapshot: modalStates.confirm 1`] = `
|
||||
<div
|
||||
className="bg-white p-3 rounded shadow"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"textAlign": "start",
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ exports[`UnenrollConfirmModal component snapshot: modalStates.finished, reason g
|
||||
<div
|
||||
className="bg-white p-3 rounded shadow"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"textAlign": "start",
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ exports[`UnenrollConfirmModal component snapshot: modalStates.finished, reason s
|
||||
<div
|
||||
className="bg-white p-3 rounded shadow"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"textAlign": "start",
|
||||
}
|
||||
}
|
||||
@@ -83,14 +83,14 @@ exports[`UnenrollConfirmModal component snapshot: modalStates.reason, should be
|
||||
<div
|
||||
className="bg-white p-3 rounded"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"textAlign": "start",
|
||||
}
|
||||
}
|
||||
>
|
||||
<ReasonPane
|
||||
reason={
|
||||
Object {
|
||||
{
|
||||
"isSkipped": false,
|
||||
"reasonProps": "other",
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`WidgetSidebar snapshots default 1`] = `
|
||||
<div
|
||||
className="widget-sidebar"
|
||||
>
|
||||
<div
|
||||
className="d-flex flex-column"
|
||||
>
|
||||
<PluginSlot
|
||||
id="widget_sidebar_plugin_slot"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,29 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { PluginSlot } from '@openedx/frontend-plugin-framework';
|
||||
|
||||
import hooks from 'widgets/ProductRecommendations/hooks';
|
||||
|
||||
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">
|
||||
<PluginSlot id="widget_sidebar_plugin_slot" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
WidgetSidebar.propTypes = {
|
||||
setSidebarShowing: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default WidgetSidebar;
|
||||
@@ -1,52 +0,0 @@
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
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(),
|
||||
}));
|
||||
jest.mock('@openedx/frontend-plugin-framework', () => ({
|
||||
PluginSlot: 'PluginSlot',
|
||||
}));
|
||||
|
||||
describe('WidgetSidebar', () => {
|
||||
beforeEach(() => jest.resetAllMocks());
|
||||
const props = {
|
||||
setSidebarShowing: jest.fn(),
|
||||
};
|
||||
|
||||
describe('snapshots', () => {
|
||||
test('default', () => {
|
||||
hooks.useShowRecommendationsFooter.mockReturnValueOnce(
|
||||
mockFooterRecommendationsHook.activeControl,
|
||||
);
|
||||
const wrapper = shallow(<WidgetSidebar {...props} />);
|
||||
|
||||
expect(props.setSidebarShowing).toHaveBeenCalledWith(true);
|
||||
expect(wrapper.snapshot).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.shallowWrapper).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.shallowWrapper).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { PluginSlot } from '@openedx/frontend-plugin-framework';
|
||||
|
||||
import hooks from 'widgets/ProductRecommendations/hooks';
|
||||
|
||||
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">
|
||||
<PluginSlot id="widget_sidebar_plugin_slot" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
WidgetSidebar.propTypes = {
|
||||
setSidebarShowing: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default WidgetSidebar;
|
||||
@@ -1,52 +0,0 @@
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
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(),
|
||||
}));
|
||||
jest.mock('@openedx/frontend-plugin-framework', () => ({
|
||||
PluginSlot: 'PluginSlot',
|
||||
}));
|
||||
|
||||
describe('WidgetSidebar', () => {
|
||||
beforeEach(() => jest.resetAllMocks());
|
||||
const props = {
|
||||
setSidebarShowing: jest.fn(),
|
||||
};
|
||||
|
||||
describe('snapshots', () => {
|
||||
test('default', () => {
|
||||
hooks.useShowRecommendationsFooter.mockReturnValueOnce(
|
||||
mockFooterRecommendationsHook.activeControl,
|
||||
);
|
||||
const wrapper = shallow(<WidgetSidebar {...props} />);
|
||||
|
||||
expect(props.setSidebarShowing).toHaveBeenCalledWith(true);
|
||||
expect(wrapper.snapshot).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.shallowWrapper).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.shallowWrapper).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -1,9 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`WidgetFooter snapshots default 1`] = `
|
||||
<div
|
||||
className="widget-footer"
|
||||
>
|
||||
<ProductRecommendations />
|
||||
</div>
|
||||
`;
|
||||
@@ -1,21 +0,0 @@
|
||||
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;
|
||||
@@ -1,45 +0,0 @@
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
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.snapshot).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.shallowWrapper).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.shallowWrapper).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`WidgetSidebar snapshots default 1`] = `
|
||||
exports[`WidgetSidebar snapshots 1`] = `
|
||||
<div
|
||||
className="widget-sidebar px-2"
|
||||
>
|
||||
23
src/containers/WidgetContainers/WidgetSidebar/index.jsx
Normal file
23
src/containers/WidgetContainers/WidgetSidebar/index.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { PluginSlot } from '@openedx/frontend-plugin-framework';
|
||||
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
export const WidgetSidebar = () => {
|
||||
const hasCourses = reduxHooks.useHasCourses();
|
||||
|
||||
const widgetSidebarClassNames = classNames('widget-sidebar', { 'px-2': !hasCourses });
|
||||
const innerWrapperClassNames = classNames('d-flex', { 'flex-column': hasCourses });
|
||||
|
||||
return (
|
||||
<div className={widgetSidebarClassNames}>
|
||||
<div className={innerWrapperClassNames}>
|
||||
<PluginSlot id="widget_sidebar_plugin_slot" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WidgetSidebar;
|
||||
18
src/containers/WidgetContainers/WidgetSidebar/index.test.jsx
Normal file
18
src/containers/WidgetContainers/WidgetSidebar/index.test.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import WidgetSidebar from '.';
|
||||
|
||||
jest.mock('widgets/LookingForChallengeWidget', () => 'LookingForChallengeWidget');
|
||||
|
||||
jest.mock('@openedx/frontend-plugin-framework', () => ({
|
||||
PluginSlot: 'PluginSlot',
|
||||
}));
|
||||
|
||||
describe('WidgetSidebar', () => {
|
||||
beforeEach(() => jest.resetAllMocks());
|
||||
|
||||
test('snapshots', () => {
|
||||
const wrapper = shallow(<WidgetSidebar />);
|
||||
expect(wrapper.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -20,8 +20,6 @@ export const initializeList = ({ user } = {}) => get(
|
||||
stringifyUrl(urls.getInitApiUrl(), { [apiKeys.user]: user }),
|
||||
);
|
||||
|
||||
export const getProgramsConfig = () => get(urls.programsConfigUrl());
|
||||
|
||||
export const updateEntitlementEnrollment = ({ uuid, courseId }) => post(
|
||||
urls.entitlementEnrollment(uuid),
|
||||
{ [apiKeys.courseRunId]: courseId },
|
||||
@@ -77,7 +75,6 @@ export default {
|
||||
initializeList,
|
||||
unenrollFromCourse,
|
||||
updateEmailSettings,
|
||||
getProgramsConfig,
|
||||
updateEntitlementEnrollment,
|
||||
deleteEntitlementEnrollment,
|
||||
logEvent,
|
||||
|
||||
@@ -22,7 +22,6 @@ export const baseAppUrl = (url) => updateUrl(getBaseUrl(), url);
|
||||
export const learningMfeUrl = (url) => updateUrl(getConfig().LEARNING_BASE_URL, url);
|
||||
|
||||
// static view url
|
||||
const programsConfigUrl = () => baseAppUrl('/config/programs');
|
||||
const programsUrl = () => baseAppUrl('/dashboard/programs');
|
||||
|
||||
export const creditPurchaseUrl = (courseId) => `${getEcommerceUrl()}/credit/checkout/${courseId}/`;
|
||||
@@ -39,6 +38,5 @@ export default StrictDict({
|
||||
getInitApiUrl,
|
||||
learningMfeUrl,
|
||||
programsUrl,
|
||||
programsConfigUrl,
|
||||
updateEmailSettings,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import * as redux from 'redux';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProduction';
|
||||
import {
|
||||
composeWithDevToolsLogOnlyInProduction,
|
||||
} from '@redux-devtools/extension';
|
||||
import { createLogger } from 'redux-logger';
|
||||
|
||||
import apiTestUtils from 'data/services/lms/fakeData/testUtils';
|
||||
@@ -14,7 +16,7 @@ export const createStore = () => {
|
||||
|
||||
const store = redux.createStore(
|
||||
reducer,
|
||||
composeWithDevTools(redux.applyMiddleware(...middleware)),
|
||||
composeWithDevToolsLogOnlyInProduction(redux.applyMiddleware(...middleware)),
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { applyMiddleware } from 'redux';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProduction';
|
||||
import {
|
||||
composeWithDevToolsLogOnlyInProduction,
|
||||
} from '@redux-devtools/extension';
|
||||
import { createLogger } from 'redux-logger';
|
||||
|
||||
import rootReducer, { actions, selectors } from 'data/redux';
|
||||
@@ -22,8 +24,8 @@ jest.mock('redux', () => ({
|
||||
applyMiddleware: (...middleware) => ({ applied: middleware }),
|
||||
createStore: (reducer, middleware) => ({ reducer, middleware }),
|
||||
}));
|
||||
jest.mock('redux-devtools-extension/logOnlyInProduction', () => ({
|
||||
composeWithDevTools: (middleware) => ({ withDevTools: middleware }),
|
||||
jest.mock('@redux-devtools/extension', () => ({
|
||||
composeWithDevToolsLogOnlyInProduction: (middleware) => ({ withDevTools: middleware }),
|
||||
}));
|
||||
|
||||
describe('store aggregator module', () => {
|
||||
@@ -37,7 +39,7 @@ describe('store aggregator module', () => {
|
||||
describe('middleware', () => {
|
||||
it('exports thunk and logger middleware, composed and applied with dev tools', () => {
|
||||
expect(createStore().middleware).toEqual(
|
||||
composeWithDevTools(applyMiddleware(thunkMiddleware, createLogger())),
|
||||
composeWithDevToolsLogOnlyInProduction(applyMiddleware(thunkMiddleware, createLogger())),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
import { logError } from '@edx/frontend-platform/logging';
|
||||
|
||||
import { RequestKeys } from 'data/constants/requests';
|
||||
import { post } from 'data/services/lms/utils';
|
||||
@@ -32,25 +31,6 @@ export const useInitializeApp = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useProgramsConfig = () => {
|
||||
const [config, setConfig] = React.useState({});
|
||||
|
||||
const fetchProgramsConfig = React.useCallback(async () => {
|
||||
try {
|
||||
const { data } = await api.getProgramsConfig();
|
||||
setConfig(data);
|
||||
} catch (error) {
|
||||
logError(`Error accessing programs configuration: ${error.message}`);
|
||||
}
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchProgramsConfig();
|
||||
}, [fetchProgramsConfig]);
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
export const useNewEntitlementEnrollment = (cardId) => {
|
||||
const { uuid } = reduxHooks.useCardEntitlementData(cardId);
|
||||
const onSuccess = module.useInitializeApp();
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
within,
|
||||
prettyDOM,
|
||||
} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import {
|
||||
initialize,
|
||||
mergeConfig,
|
||||
@@ -42,9 +41,7 @@ jest.unmock('react-redux');
|
||||
jest.unmock('reselect');
|
||||
jest.unmock('hooks');
|
||||
|
||||
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('containers/WidgetContainers/WidgetSidebar', () => jest.fn(() => 'widget-sidebar'));
|
||||
jest.mock('components/NoticesWrapper', () => 'notices-wrapper');
|
||||
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
@@ -61,10 +58,6 @@ jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
getLoginRedirectUrl: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('ExperimentContext', () => ({
|
||||
ExperimentProvider: 'div'
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-enterprise-hotjar', () => ({
|
||||
initializeHotjar: jest.fn(),
|
||||
}));
|
||||
|
||||
@@ -24,14 +24,14 @@ exports[`LookingForChallengeWidget snapshots default 1`] = `
|
||||
>
|
||||
<format-message-function
|
||||
message={
|
||||
Object {
|
||||
{
|
||||
"defaultMessage": "Find a course {arrow}",
|
||||
"description": "Button to explore more courses",
|
||||
"id": "WidgetSidebar.findCoursesButton",
|
||||
}
|
||||
}
|
||||
values={
|
||||
Object {
|
||||
{
|
||||
"arrow": <Icon
|
||||
className="mx-1"
|
||||
/>,
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
// 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",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
`;
|
||||
@@ -1,19 +0,0 @@
|
||||
import { get, stringifyUrl } from 'data/services/lms/utils';
|
||||
import urls from 'data/services/lms/urls';
|
||||
|
||||
export const crossProductAndAmplitudeRecommendationsUrl = (courseId) => `${urls.getApiUrl()}/edx_recommendations/learner_dashboard/cross_product/${courseId}/`;
|
||||
export const amplitudeRecommendationsUrl = () => `${urls.getApiUrl()}/edx_recommendations/learner_dashboard/amplitude/v2/`;
|
||||
export const recommendationsContextUrl = () => `${urls.getApiUrl()}/edx_recommendations/learner_dashboard/recommendations_context/`;
|
||||
|
||||
const fetchRecommendationsContext = () => get(stringifyUrl(recommendationsContextUrl()));
|
||||
|
||||
const fetchCrossProductRecommendations = (courseId) => (
|
||||
get(stringifyUrl(crossProductAndAmplitudeRecommendationsUrl(courseId)))
|
||||
);
|
||||
const fetchAmplitudeRecommendations = () => get(stringifyUrl(amplitudeRecommendationsUrl()));
|
||||
|
||||
export default {
|
||||
fetchCrossProductRecommendations,
|
||||
fetchAmplitudeRecommendations,
|
||||
fetchRecommendationsContext,
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
import { get, stringifyUrl } from 'data/services/lms/utils';
|
||||
|
||||
import api, { crossProductAndAmplitudeRecommendationsUrl, amplitudeRecommendationsUrl, recommendationsContextUrl } from './api';
|
||||
|
||||
jest.mock('data/services/lms/utils', () => ({
|
||||
stringifyUrl: (...args) => ({ stringifyUrl: args }),
|
||||
get: (...args) => ({ get: args }),
|
||||
}));
|
||||
|
||||
describe('productRecommendationCourses api', () => {
|
||||
describe('fetchCrossProductRecommendations', () => {
|
||||
it('calls get with the correct recommendation courses URL', () => {
|
||||
expect(api.fetchCrossProductRecommendations('CourseRunKey')).toEqual(
|
||||
get(stringifyUrl(crossProductAndAmplitudeRecommendationsUrl('CourseRunKey'))),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchAmplitudeRecommendations', () => {
|
||||
it('calls get with the correct recommendation courses URL', () => {
|
||||
expect(api.fetchAmplitudeRecommendations()).toEqual(
|
||||
get(stringifyUrl(amplitudeRecommendationsUrl())),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchRecommendationsContext', () => {
|
||||
it('calls get with the correct recommendation courses URL', () => {
|
||||
expect(api.fetchRecommendationsContext()).toEqual(
|
||||
get(stringifyUrl(recommendationsContextUrl())),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,46 +0,0 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Container } from '@openedx/paragon';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import messages from '../messages';
|
||||
import { courseShape, courseTypeToProductTypeMap } from '../utils';
|
||||
import ProductCardContainer from './ProductCardContainer';
|
||||
|
||||
const LoadedView = ({ crossProductCourses, openCourses }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const includesCrossProductTypes = crossProductCourses.length === 2;
|
||||
|
||||
const finalProductList = useMemo(() => {
|
||||
if (includesCrossProductTypes) {
|
||||
const openCourseList = openCourses.slice(0, 2);
|
||||
return crossProductCourses.concat(openCourseList);
|
||||
}
|
||||
return openCourses;
|
||||
}, [crossProductCourses, openCourses, includesCrossProductTypes]);
|
||||
|
||||
const courseTypes = [...new Set(finalProductList.map((item) => courseTypeToProductTypeMap[item.courseType]))];
|
||||
|
||||
return (
|
||||
<Container
|
||||
size="lg"
|
||||
className="recommendations-container pt-sm-5 pt-4.5 pb-2 pb-sm-4.5"
|
||||
>
|
||||
<h2>
|
||||
{formatMessage(messages.recommendationsHeading)}
|
||||
</h2>
|
||||
<ProductCardContainer finalProductList={finalProductList} courseTypes={courseTypes} />
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
LoadedView.propTypes = {
|
||||
crossProductCourses: PropTypes.arrayOf(
|
||||
PropTypes.shape(courseShape),
|
||||
).isRequired,
|
||||
openCourses: PropTypes.arrayOf(
|
||||
PropTypes.shape(courseShape),
|
||||
).isRequired,
|
||||
};
|
||||
|
||||
export default LoadedView;
|
||||
@@ -1,34 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { mockCrossProductCourses, mockOpenCourses } from '../testData';
|
||||
import LoadedView from './LoadedView';
|
||||
|
||||
describe('ProductRecommendations LoadedView', () => {
|
||||
it('matches snapshot', () => {
|
||||
expect(
|
||||
shallow(
|
||||
<LoadedView
|
||||
crossProductCourses={mockCrossProductCourses}
|
||||
openCourses={mockOpenCourses}
|
||||
/>,
|
||||
).snapshot,
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
describe('with less than 2 cross product courses', () => {
|
||||
it('passes in one course type and 4 open courses to the ProductCardContainer props', () => {
|
||||
const wrapper = shallow(
|
||||
<LoadedView
|
||||
crossProductCourses={[mockCrossProductCourses[0]]}
|
||||
openCourses={mockOpenCourses}
|
||||
/>,
|
||||
);
|
||||
|
||||
const productCardContainerProps = wrapper.instance.findByType('ProductCardContainer')[0].props;
|
||||
|
||||
expect(productCardContainerProps.courseTypes.length).toEqual(1);
|
||||
expect(productCardContainerProps.courseTypes[0]).toEqual('Course');
|
||||
expect(productCardContainerProps.finalProductList).toEqual(mockOpenCourses);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,8 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Skeleton } from '@openedx/paragon';
|
||||
|
||||
export const LoadingView = () => (
|
||||
<Skeleton height={100} />
|
||||
);
|
||||
|
||||
export default LoadingView;
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import LoadingView from './LoadingView';
|
||||
|
||||
describe('ProductRecommendations LoadingView', () => {
|
||||
it('matches snapshot', () => {
|
||||
expect(shallow(<LoadingView />).snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,98 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Badge,
|
||||
Card,
|
||||
Truncate,
|
||||
Hyperlink,
|
||||
} from '@openedx/paragon';
|
||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
|
||||
import { trackProductCardClicked, trackCourseCardClicked } from '../optimizelyExperiment';
|
||||
import { productCardClicked, discoveryCardClicked } from '../track';
|
||||
import { bootCamp, executiveEducation } from '../constants';
|
||||
|
||||
const ProductCard = ({
|
||||
title,
|
||||
subtitle,
|
||||
headerImage,
|
||||
courseRunKey,
|
||||
schoolLogo,
|
||||
courseType,
|
||||
url,
|
||||
}) => {
|
||||
const handleClick = (type, link) => {
|
||||
const crossProductTypes = [executiveEducation, bootCamp];
|
||||
const userId = getAuthenticatedUser().userId.toString();
|
||||
|
||||
if (crossProductTypes.includes(type)) {
|
||||
trackProductCardClicked(userId);
|
||||
productCardClicked(courseRunKey, title, type, link);
|
||||
} else {
|
||||
trackCourseCardClicked(userId);
|
||||
discoveryCardClicked(courseRunKey, title, link);
|
||||
}
|
||||
};
|
||||
|
||||
const getRedirectUrl = (link) => {
|
||||
const urlObj = new URL(link);
|
||||
const hasQueryStringParameters = urlObj.search !== '';
|
||||
|
||||
if (hasQueryStringParameters) {
|
||||
return `${link}&linked_from=recommender`;
|
||||
}
|
||||
|
||||
return `${link}?linked_from=recommender`;
|
||||
};
|
||||
|
||||
const redirectUrl = getRedirectUrl(url);
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="base-card d-flex text-decoration-none"
|
||||
as={Hyperlink}
|
||||
destination={redirectUrl}
|
||||
onClick={() => {
|
||||
handleClick(courseType, redirectUrl);
|
||||
}}
|
||||
isClickable
|
||||
>
|
||||
<Card.ImageCap
|
||||
src={headerImage}
|
||||
srcAlt={`header image for ${title}`}
|
||||
logoSrc={schoolLogo}
|
||||
logoAlt={`logo for ${subtitle}`}
|
||||
/>
|
||||
<Card.Header
|
||||
className="mt-2"
|
||||
title={(
|
||||
<Truncate lines={3} ellipsis="…" className="product-card-title font-weight-bold">
|
||||
{title}
|
||||
</Truncate>
|
||||
)}
|
||||
subtitle={(
|
||||
<Truncate lines={1} className="product-card-subtitle font-weight-normal">
|
||||
{subtitle}
|
||||
</Truncate>
|
||||
)}
|
||||
/>
|
||||
<Card.Section>
|
||||
<div className="product-badge position-absolute">
|
||||
<Badge className="bg-light-500 text-dark-500">{courseType}</Badge>
|
||||
</div>
|
||||
</Card.Section>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
ProductCard.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
subtitle: PropTypes.string.isRequired,
|
||||
headerImage: PropTypes.string.isRequired,
|
||||
courseRunKey: PropTypes.string.isRequired,
|
||||
schoolLogo: PropTypes.string.isRequired,
|
||||
courseType: PropTypes.string.isRequired,
|
||||
url: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ProductCard;
|
||||
@@ -1,83 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { mockCrossProductCourses, mockOpenCourses, mockFallbackOpenCourse } from '../testData';
|
||||
import { trackProductCardClicked, trackCourseCardClicked } from '../optimizelyExperiment';
|
||||
import { productCardClicked, discoveryCardClicked } from '../track';
|
||||
import ProductCard from './ProductCard';
|
||||
import { courseTypeToProductTypeMap } from '../utils';
|
||||
|
||||
jest.mock('../optimizelyExperiment', () => ({
|
||||
trackProductCardClicked: jest.fn(),
|
||||
trackCourseCardClicked: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../track', () => ({
|
||||
productCardClicked: jest.fn(),
|
||||
discoveryCardClicked: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
getAuthenticatedUser: jest.fn(() => ({ userId: '1' })),
|
||||
}));
|
||||
|
||||
describe('ProductRecommendations ProductCard', () => {
|
||||
const getProps = (course) => {
|
||||
const {
|
||||
title,
|
||||
owners: [{ name: subtitle }],
|
||||
image: { src: headerImage },
|
||||
owners: [{ logoImageUrl: schoolLogo }],
|
||||
} = course;
|
||||
|
||||
return {
|
||||
title,
|
||||
subtitle,
|
||||
headerImage,
|
||||
schoolLogo,
|
||||
courseType: courseTypeToProductTypeMap[course.courseType],
|
||||
courseRunKey: course.courseRunKey,
|
||||
url: course.marketingUrl,
|
||||
};
|
||||
};
|
||||
|
||||
const crossProductProps = getProps(mockCrossProductCourses[0]);
|
||||
const openCourseProps = getProps(mockOpenCourses[0]);
|
||||
const fallbackOpenCourseProps = getProps(mockFallbackOpenCourse[0]);
|
||||
|
||||
it('matches snapshot', () => {
|
||||
expect(shallow(<ProductCard {...crossProductProps} />).snapshot).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('has the query string parameter attached to a fallback recommendations url', () => {
|
||||
const wrapper = shallow(<ProductCard {...fallbackOpenCourseProps} />);
|
||||
const cardUrl = wrapper.instance.findByType('Card')[0].props.destination;
|
||||
|
||||
expect(cardUrl).toEqual('https://www.edx.org/course/some-course?linked_from=recommender');
|
||||
});
|
||||
|
||||
it('send outs experiment events related to open courses when clicked', () => {
|
||||
const wrapper = shallow(<ProductCard {...openCourseProps} />);
|
||||
const { courseRunKey, title, url } = openCourseProps;
|
||||
|
||||
wrapper.instance.props.onClick();
|
||||
|
||||
expect(trackCourseCardClicked).toHaveBeenCalledWith('1');
|
||||
expect(discoveryCardClicked).toHaveBeenCalledWith(courseRunKey, title, `${url}&linked_from=recommender`);
|
||||
});
|
||||
|
||||
it('send outs experiment events related to cross product courses when clicked', () => {
|
||||
const wrapper = shallow(<ProductCard {...crossProductProps} />);
|
||||
const {
|
||||
courseRunKey,
|
||||
title,
|
||||
courseType,
|
||||
url,
|
||||
} = crossProductProps;
|
||||
|
||||
wrapper.instance.props.onClick();
|
||||
|
||||
expect(trackProductCardClicked).toHaveBeenCalledWith('1');
|
||||
expect(productCardClicked).toHaveBeenCalledWith(courseRunKey, title, courseType, `${url}&linked_from=recommender`);
|
||||
});
|
||||
});
|
||||
@@ -1,48 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { courseShape, courseTypeToProductTypeMap } from '../utils';
|
||||
import { course } from '../constants';
|
||||
import ProductCard from './ProductCard';
|
||||
import ProductCardHeader from './ProductCardHeader';
|
||||
|
||||
const ProductCardContainer = ({ finalProductList, courseTypes }) => (
|
||||
<div className="product-card-container d-flex">
|
||||
{finalProductList
|
||||
&& courseTypes.map((type) => (
|
||||
<div key={type}>
|
||||
<ProductCardHeader courseType={type} />
|
||||
<div
|
||||
className={classNames('d-flex', {
|
||||
'course-subcontainer': type === course,
|
||||
})}
|
||||
>
|
||||
{finalProductList
|
||||
.filter((courseObj) => courseTypeToProductTypeMap[courseObj.courseType] === type)
|
||||
.map((item) => (
|
||||
<ProductCard
|
||||
key={item.title}
|
||||
url={item.marketingUrl}
|
||||
title={item.title}
|
||||
subtitle={item.owners[0].name}
|
||||
headerImage={item.image.src}
|
||||
courseRunKey={item.courseRunKey}
|
||||
schoolLogo={item.owners[0].logoImageUrl}
|
||||
courseType={type}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
ProductCardContainer.propTypes = {
|
||||
finalProductList: PropTypes.arrayOf(
|
||||
PropTypes.shape(courseShape),
|
||||
).isRequired,
|
||||
courseTypes: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
};
|
||||
|
||||
export default ProductCardContainer;
|
||||
@@ -1,44 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { mockCrossProductCourses, mockOpenCourses } from '../testData';
|
||||
import ProductCardContainer from './ProductCardContainer';
|
||||
import { executiveEducation, bootCamp, course } from '../constants';
|
||||
|
||||
describe('ProductRecommendations ProductCardContainer', () => {
|
||||
const props = {
|
||||
finalProductList: [...mockCrossProductCourses, ...mockOpenCourses],
|
||||
courseTypes: [executiveEducation, bootCamp, course],
|
||||
};
|
||||
|
||||
it('matches snapshot', () => {
|
||||
expect(shallow(<ProductCardContainer {...props} />).snapshot).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('with finalCourseList containing cross product and open courses', () => {
|
||||
it('renders 3 ProductCardHeaders with the 3 different course types', () => {
|
||||
const wrapper = shallow(<ProductCardContainer {...props} />);
|
||||
const productCardHeaders = wrapper.instance.findByType('ProductCardHeader');
|
||||
|
||||
expect(productCardHeaders.length).toEqual(3);
|
||||
productCardHeaders.forEach((header, index) => {
|
||||
expect(header.props.courseType).toEqual(props.courseTypes[index]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with finalCourseList containing only open courses', () => {
|
||||
it('renders 1 ProductHeader with the one course type', () => {
|
||||
const openCoursesProps = {
|
||||
finalProductList: [...mockOpenCourses, ...mockOpenCourses],
|
||||
courseTypes: ['Course'],
|
||||
};
|
||||
|
||||
const wrapper = shallow(<ProductCardContainer {...openCoursesProps} />);
|
||||
const productCardHeaders = wrapper.instance.findByType('ProductCardHeader');
|
||||
|
||||
expect(productCardHeaders.length).toEqual(1);
|
||||
expect(productCardHeaders[0].props.courseType).toEqual(openCoursesProps.courseTypes[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user