Compare commits
63 Commits
frontend-b
...
release/ul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b1c56e850 | ||
|
|
0db621b134 | ||
|
|
aaf2e36fe9 | ||
|
|
b527fbcfba | ||
|
|
fa3f7b27cf | ||
|
|
3b0c58f376 | ||
|
|
f903392ca1 | ||
|
|
89032f09f4 | ||
|
|
03f146cce9 | ||
|
|
7dd2bda86e | ||
|
|
410f2f730f | ||
|
|
de13749443 | ||
|
|
0e66b2031d | ||
|
|
697c9ff2c8 | ||
|
|
fdb5d2f68c | ||
|
|
7cf79f8931 | ||
|
|
73b7b7f5d0 | ||
|
|
516544c7cc | ||
|
|
2134df5478 | ||
|
|
5345d2eef2 | ||
|
|
d94ac8dd62 | ||
|
|
ff925b06f1 | ||
|
|
2696486e5b | ||
|
|
5854b00d08 | ||
|
|
13e9b1a85f | ||
|
|
8116956a4b | ||
|
|
8d23e7585b | ||
|
|
b63a40006e | ||
|
|
92243063b9 | ||
|
|
a2673399aa | ||
|
|
cedcb4172d | ||
|
|
9e4faf1569 | ||
|
|
3a9acc981b | ||
|
|
e3c18698d8 | ||
|
|
eb38beedc3 | ||
|
|
01faf5a30a | ||
|
|
f4807614e2 | ||
|
|
ab448e52f2 | ||
|
|
3383016176 | ||
|
|
054cd57d4b | ||
|
|
0fc3cc4d53 | ||
|
|
e5c1244e59 | ||
|
|
3a389e14e1 | ||
|
|
6de409d7cc | ||
|
|
517b8424b3 | ||
|
|
49e527d810 | ||
|
|
fdc58a671a | ||
|
|
90aa652ca6 | ||
|
|
05a08101a2 | ||
|
|
cf94ea99b4 | ||
|
|
7f1e509ecf | ||
|
|
31c5445722 | ||
|
|
2acf7fbd73 | ||
|
|
cae7b1bba0 | ||
|
|
6ae8180f99 | ||
|
|
ab0f139d75 | ||
|
|
8a0b9dca5d | ||
|
|
2e59e24876 | ||
|
|
75865290bf | ||
|
|
57fe161b16 | ||
|
|
5c4dfd5de3 | ||
|
|
b0daefa2bf | ||
|
|
42a21180c7 |
10
.dockerignore
Executable file
10
.dockerignore
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
README.md
|
||||||
|
LICENSE
|
||||||
|
.babelrc
|
||||||
|
.eslintignore
|
||||||
|
.eslintrc.json
|
||||||
|
.gitignore
|
||||||
|
.npmignore
|
||||||
|
commitlint.config.js
|
||||||
46
.env
Normal file
46
.env
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
NODE_ENV='production'
|
||||||
|
NODE_PATH=./src
|
||||||
|
BASE_URL=''
|
||||||
|
LMS_BASE_URL=''
|
||||||
|
ECOMMERCE_BASE_URL=''
|
||||||
|
LOGIN_URL=''
|
||||||
|
LOGOUT_URL=''
|
||||||
|
CSRF_TOKEN_API_PATH=''
|
||||||
|
REFRESH_ACCESS_TOKEN_ENDPOINT=''
|
||||||
|
DATA_API_BASE_URL=''
|
||||||
|
SEGMENT_KEY=''
|
||||||
|
FEATURE_FLAGS={}
|
||||||
|
ACCESS_TOKEN_COOKIE_NAME=''
|
||||||
|
NEW_RELIC_APP_ID=''
|
||||||
|
NEW_RELIC_LICENSE_KEY=''
|
||||||
|
SITE_NAME=''
|
||||||
|
MARKETING_SITE_BASE_URL=''
|
||||||
|
SUPPORT_URL=''
|
||||||
|
CONTACT_URL=''
|
||||||
|
OPEN_SOURCE_URL=''
|
||||||
|
TERMS_OF_SERVICE_URL=''
|
||||||
|
PRIVACY_POLICY_URL=''
|
||||||
|
FACEBOOK_URL=''
|
||||||
|
TWITTER_URL=''
|
||||||
|
YOU_TUBE_URL=''
|
||||||
|
LINKED_IN_URL=''
|
||||||
|
REDDIT_URL=''
|
||||||
|
APPLE_APP_STORE_URL=''
|
||||||
|
GOOGLE_PLAY_URL=''
|
||||||
|
ENTERPRISE_MARKETING_URL=''
|
||||||
|
ENTERPRISE_MARKETING_UTM_SOURCE=''
|
||||||
|
ENTERPRISE_MARKETING_UTM_CAMPAIGN=''
|
||||||
|
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM=''
|
||||||
|
LEARNING_BASE_URL=''
|
||||||
|
HOTJAR_APP_ID=''
|
||||||
|
HOTJAR_VERSION='6'
|
||||||
|
HOTJAR_DEBUG=''
|
||||||
|
ACCOUNT_SETTINGS_URL=''
|
||||||
|
ACCOUNT_PROFILE_URL=''
|
||||||
|
ENABLE_NOTICES=''
|
||||||
|
CAREER_LINK_URL=''
|
||||||
|
ENABLE_EDX_PERSONAL_DASHBOARD=false
|
||||||
|
ENABLE_PROGRAMS=false
|
||||||
|
NON_BROWSABLE_COURSES=false
|
||||||
|
# Fallback in local style files
|
||||||
|
PARAGON_THEME_URLS={}
|
||||||
52
.env.development
Normal file
52
.env.development
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
NODE_ENV='development'
|
||||||
|
PORT=1996
|
||||||
|
BASE_URL='localhost:1996'
|
||||||
|
LMS_BASE_URL='http://localhost:18000'
|
||||||
|
ECOMMERCE_BASE_URL='http://localhost:18130'
|
||||||
|
LOGIN_URL='http://localhost:18000/login'
|
||||||
|
LOGOUT_URL='http://localhost:18000/logout'
|
||||||
|
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
|
||||||
|
LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg
|
||||||
|
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
|
||||||
|
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
||||||
|
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
||||||
|
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||||
|
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
|
||||||
|
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||||
|
SITE_NAME=localhost
|
||||||
|
DATA_API_BASE_URL='http://localhost:8000'
|
||||||
|
// LMS_CLIENT_ID should match the lms DOT client application id your LMS containe
|
||||||
|
LMS_CLIENT_ID='login-service-client-id'
|
||||||
|
SEGMENT_KEY=''
|
||||||
|
FEATURE_FLAGS={}
|
||||||
|
MARKETING_SITE_BASE_URL='http://localhost:18000'
|
||||||
|
SUPPORT_URL=''
|
||||||
|
CONTACT_URL='http://localhost:18000/contact'
|
||||||
|
OPEN_SOURCE_URL='http://localhost:18000/openedx'
|
||||||
|
TERMS_OF_SERVICE_URL='http://localhost:18000/terms-of-service'
|
||||||
|
PRIVACY_POLICY_URL='http://localhost:18000/privacy-policy'
|
||||||
|
FACEBOOK_URL='https://www.facebook.com'
|
||||||
|
TWITTER_URL='https://twitter.com'
|
||||||
|
YOU_TUBE_URL='https://www.youtube.com'
|
||||||
|
LINKED_IN_URL='https://www.linkedin.com'
|
||||||
|
REDDIT_URL='https://www.reddit.com'
|
||||||
|
APPLE_APP_STORE_URL='https://www.apple.com/ios/app-store/'
|
||||||
|
GOOGLE_PLAY_URL='https://play.google.com/store'
|
||||||
|
ENTERPRISE_MARKETING_URL='http://example.com'
|
||||||
|
ENTERPRISE_MARKETING_UTM_SOURCE='example.com'
|
||||||
|
ENTERPRISE_MARKETING_UTM_CAMPAIGN='example.com Referral'
|
||||||
|
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='Footer'
|
||||||
|
LEARNING_BASE_URL='http://localhost:2000'
|
||||||
|
SESSION_COOKIE_DOMAIN='localhost'
|
||||||
|
HOTJAR_APP_ID=''
|
||||||
|
HOTJAR_VERSION='6'
|
||||||
|
HOTJAR_DEBUG=''
|
||||||
|
ACCOUNT_SETTINGS_URL='http://localhost:1997'
|
||||||
|
ACCOUNT_PROFILE_URL='http://localhost:1995'
|
||||||
|
ENABLE_NOTICES=''
|
||||||
|
CAREER_LINK_URL=''
|
||||||
|
ENABLE_EDX_PERSONAL_DASHBOARD=false
|
||||||
|
ENABLE_PROGRAMS=false
|
||||||
|
NON_BROWSABLE_COURSES=false
|
||||||
|
# Fallback in local style files
|
||||||
|
PARAGON_THEME_URLS={}
|
||||||
50
.env.test
Normal file
50
.env.test
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
NODE_ENV='test'
|
||||||
|
PORT=1996
|
||||||
|
BASE_URL='localhost:1996'
|
||||||
|
LMS_BASE_URL='http://localhost:18000'
|
||||||
|
ECOMMERCE_BASE_URL='http://localhost:18130'
|
||||||
|
LOGIN_URL='http://localhost:18000/login'
|
||||||
|
LOGOUT_URL='http://localhost:18000/logout'
|
||||||
|
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
|
||||||
|
LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg
|
||||||
|
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
|
||||||
|
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
||||||
|
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
||||||
|
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||||
|
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
|
||||||
|
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||||
|
SITE_NAME=localhost
|
||||||
|
DATA_API_BASE_URL='http://localhost:8000'
|
||||||
|
// LMS_CLIENT_ID should match the lms DOT client application id your LMS containe
|
||||||
|
LMS_CLIENT_ID='login-service-client-id'
|
||||||
|
SEGMENT_KEY=''
|
||||||
|
FEATURE_FLAGS={}
|
||||||
|
MARKETING_SITE_BASE_URL='http://localhost:18000'
|
||||||
|
SUPPORT_URL=''
|
||||||
|
CONTACT_URL='http://localhost:18000/contact'
|
||||||
|
OPEN_SOURCE_URL='http://localhost:18000/openedx'
|
||||||
|
TERMS_OF_SERVICE_URL='http://localhost:18000/terms-of-service'
|
||||||
|
PRIVACY_POLICY_URL='http://localhost:18000/privacy-policy'
|
||||||
|
FACEBOOK_URL='https://www.facebook.com'
|
||||||
|
TWITTER_URL='https://twitter.com'
|
||||||
|
YOU_TUBE_URL='https://www.youtube.com'
|
||||||
|
LINKED_IN_URL='https://www.linkedin.com'
|
||||||
|
REDDIT_URL='https://www.reddit.com'
|
||||||
|
APPLE_APP_STORE_URL='https://www.apple.com/ios/app-store/'
|
||||||
|
GOOGLE_PLAY_URL='https://play.google.com/store'
|
||||||
|
ENTERPRISE_MARKETING_URL='http://example.com'
|
||||||
|
ENTERPRISE_MARKETING_UTM_SOURCE='example.com'
|
||||||
|
ENTERPRISE_MARKETING_UTM_CAMPAIGN='example.com Referral'
|
||||||
|
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='Footer'
|
||||||
|
LEARNING_BASE_URL='http://localhost:2000'
|
||||||
|
HOTJAR_APP_ID='hot-jar-app-id'
|
||||||
|
HOTJAR_VERSION='6'
|
||||||
|
HOTJAR_DEBUG=''
|
||||||
|
ACCOUNT_SETTINGS_URL='http://account-settings-url.test'
|
||||||
|
ACCOUNT_PROFILE_URL='http://account-profile-url.test'
|
||||||
|
ENABLE_NOTICES=''
|
||||||
|
CAREER_LINK_URL=''
|
||||||
|
ENABLE_EDX_PERSONAL_DASHBOARD=true
|
||||||
|
ENABLE_PROGRAMS=false
|
||||||
|
NON_BROWSABLE_COURSES=false
|
||||||
|
PARAGON_THEME_URLS={}
|
||||||
5
.eslintignore
Executable file
5
.eslintignore
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
coverage/*
|
||||||
|
dist/
|
||||||
|
node_modules/
|
||||||
|
src/postcss.config.js
|
||||||
|
src/segment.js
|
||||||
22
.eslintrc.js
Normal file
22
.eslintrc.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
const { createConfig } = require('@openedx/frontend-build');
|
||||||
|
|
||||||
|
const config = createConfig('eslint', {
|
||||||
|
rules: {
|
||||||
|
'import/no-named-as-default': 'off',
|
||||||
|
'import/no-named-as-default-member': 'off',
|
||||||
|
'import/no-self-import': 'off',
|
||||||
|
'import/no-import-module-exports': 'off',
|
||||||
|
'spaced-comment': ['error', 'always', { 'block': { 'exceptions': ['*'] } }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
config.settings = {
|
||||||
|
"import/resolver": {
|
||||||
|
node: {
|
||||||
|
paths: ["src", "node_modules"],
|
||||||
|
extensions: [".js", ".jsx"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.snap linguist-generated=false
|
||||||
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -1 +0,0 @@
|
|||||||
* @openedx/2U-aperture
|
|
||||||
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@@ -14,10 +14,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup Nodejs
|
- name: Setup Nodejs
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
|
|
||||||
@@ -30,6 +30,9 @@ jobs:
|
|||||||
- name: Lint
|
- name: Lint
|
||||||
run: npm run lint
|
run: npm run lint
|
||||||
|
|
||||||
|
- name: Type check
|
||||||
|
run: npm run types
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: npm run test
|
run: npm run test
|
||||||
|
|
||||||
|
|||||||
30
.gitignore
vendored
30
.gitignore
vendored
@@ -1,15 +1,29 @@
|
|||||||
|
.DS_Store
|
||||||
|
.eslintcache
|
||||||
|
env.config.*
|
||||||
node_modules
|
node_modules
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
coverage
|
coverage
|
||||||
module.config.js
|
|
||||||
dist/
|
dist/
|
||||||
/*.tgz
|
public/samples/
|
||||||
|
|
||||||
### i18n ###
|
### pyenv ###
|
||||||
src/i18n/transifex_input.json
|
.python-version
|
||||||
|
|
||||||
### Editors ###
|
### Emacs ###
|
||||||
.DS_Store
|
|
||||||
*~
|
*~
|
||||||
/temp
|
*.swo
|
||||||
/.vscode
|
*.swp
|
||||||
|
|
||||||
|
### Development environments ###
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# Local package dependencies
|
||||||
|
module.config.js
|
||||||
|
|
||||||
|
### transifex ###
|
||||||
|
src/i18n/transifex_input.json
|
||||||
|
temp
|
||||||
|
src/i18n/messages
|
||||||
16
.npmignore
16
.npmignore
@@ -1,6 +1,12 @@
|
|||||||
__mocks__
|
.eslintignore
|
||||||
|
.eslintrc.json
|
||||||
|
.gitignore
|
||||||
|
docker-compose.yml
|
||||||
|
Dockerfile
|
||||||
|
Makefile
|
||||||
|
npm-debug.log
|
||||||
|
|
||||||
|
config
|
||||||
|
coverage
|
||||||
node_modules
|
node_modules
|
||||||
*.test.js
|
public
|
||||||
*.test.jsx
|
|
||||||
*.test.ts
|
|
||||||
*.test.tsx
|
|
||||||
|
|||||||
19
Makefile
19
Makefile
@@ -24,19 +24,6 @@ test.npm.%: validate-no-uncommitted-package-lock-changes
|
|||||||
requirements: ## install ci requirements
|
requirements: ## install ci requirements
|
||||||
npm ci
|
npm ci
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf dist
|
|
||||||
|
|
||||||
build: clean
|
|
||||||
tsc --project tsconfig.build.json
|
|
||||||
tsc-alias -p tsconfig.build.json
|
|
||||||
find src -type f \( -name '*.scss' -o -name '*.png' -o -name '*.svg' \) -exec sh -c '\
|
|
||||||
for f in "$$@"; do \
|
|
||||||
d="dist/$${f#src/}"; \
|
|
||||||
mkdir -p "$$(dirname "$$d")"; \
|
|
||||||
cp "$$f" "$$d"; \
|
|
||||||
done' sh {} +
|
|
||||||
|
|
||||||
i18n.extract:
|
i18n.extract:
|
||||||
# Pulling display strings from .jsx files into .json files...
|
# Pulling display strings from .jsx files into .json files...
|
||||||
rm -rf $(transifex_temp)
|
rm -rf $(transifex_temp)
|
||||||
@@ -58,11 +45,13 @@ pull_translations:
|
|||||||
mkdir src/i18n/messages
|
mkdir src/i18n/messages
|
||||||
cd src/i18n/messages \
|
cd src/i18n/messages \
|
||||||
&& atlas pull $(ATLAS_OPTIONS) \
|
&& atlas pull $(ATLAS_OPTIONS) \
|
||||||
translations/frontend-base/src/i18n/messages:frontend-base \
|
translations/frontend-platform/src/i18n/messages:frontend-platform \
|
||||||
translations/paragon/src/i18n/messages:paragon \
|
translations/paragon/src/i18n/messages:paragon \
|
||||||
|
translations/frontend-component-header/src/i18n/messages:frontend-component-header \
|
||||||
|
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
|
||||||
translations/frontend-app-learner-dashboard/src/i18n/messages:frontend-app-learner-dashboard
|
translations/frontend-app-learner-dashboard/src/i18n/messages:frontend-app-learner-dashboard
|
||||||
|
|
||||||
$(intl_imports) frontend-base paragon frontend-app-learner-dashboard
|
$(intl_imports) frontend-platform paragon frontend-component-header frontend-component-footer frontend-app-learner-dashboard
|
||||||
|
|
||||||
# This target is used by CI.
|
# This target is used by CI.
|
||||||
validate-no-uncommitted-package-lock-changes:
|
validate-no-uncommitted-package-lock-changes:
|
||||||
|
|||||||
19
README.rst
19
README.rst
@@ -18,7 +18,7 @@ frontend-app-learner-dashboard
|
|||||||
The Learner Home app is a microfrontend (MFE) course listing experience for the Open edX Learning Management System
|
The Learner Home app is a microfrontend (MFE) course listing experience for the Open edX Learning Management System
|
||||||
(LMS). This experience was designed to provide a clean and functional interface to allow learners to view all of their
|
(LMS). This experience was designed to provide a clean and functional interface to allow learners to view all of their
|
||||||
open enrollments, as well as take relevant actions on those enrollments. It also serves as host to a number of exposed
|
open enrollments, as well as take relevant actions on those enrollments. It also serves as host to a number of exposed
|
||||||
"widget" containers to provide upsell and discovery widgets as sidebar components.
|
"widget" containers to provide upsell and discovery widgets as sidebar/footer components.
|
||||||
|
|
||||||
Quickstart
|
Quickstart
|
||||||
----------
|
----------
|
||||||
@@ -30,10 +30,21 @@ To start the MFE and enable the feature in LMS:
|
|||||||
From there, simply load the configured address/port. You should be prompted to log into your LMS if you are not
|
From there, simply load the configured address/port. You should be prompted to log into your LMS if you are not
|
||||||
already, and then redirected to your home page.
|
already, and then redirected to your home page.
|
||||||
|
|
||||||
Widgets
|
Plugins
|
||||||
-------
|
-------
|
||||||
This MFE can be customized with widgets. The parts of this MFE that can be customized in that manner are documented
|
This MFE can be customized using `Frontend Plugin Framework <https://github.com/openedx/frontend-plugin-framework>`_.
|
||||||
`here </src/slots>`_.
|
|
||||||
|
The parts of this MFE that can be customized in that manner are documented `here </src/plugin-slots>`_.
|
||||||
|
|
||||||
|
Contributing
|
||||||
|
------------
|
||||||
|
|
||||||
|
Contributions are very welcome. Please read `So you want to contribute to Open edX <https://docs.openedx.org/en/latest/developers/quickstarts/so_you_want_to_contribute.html>`_ for details on how to get started as an Open edX contributor.
|
||||||
|
|
||||||
|
This project is currently accepting all types of contributions — bug fixes, security fixes, maintenance work, or new features.
|
||||||
|
However, if you intend to add a new feature, make sure it has gone through the `Product Review process <https://openedx.atlassian.net/wiki/spaces/COMM/pages/3875962884/How+to+submit+an+open+source+contribution+for+Product+Review>`_.
|
||||||
|
|
||||||
|
When proposing a change, create an issue in this repo to get the discussion started.
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
|||||||
10
app.d.ts
vendored
10
app.d.ts
vendored
@@ -1,10 +0,0 @@
|
|||||||
/// <reference types="@openedx/frontend-base" />
|
|
||||||
|
|
||||||
declare module 'site.config' {
|
|
||||||
export default SiteConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '*.svg' {
|
|
||||||
const content: string;
|
|
||||||
export default content;
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
const { createConfig } = require('@openedx/frontend-base/tools');
|
|
||||||
|
|
||||||
module.exports = createConfig('babel');
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
|
|
||||||
const { createLintConfig } = require('@openedx/frontend-base/tools');
|
|
||||||
|
|
||||||
module.exports = createLintConfig(
|
|
||||||
{
|
|
||||||
files: [
|
|
||||||
'src/**/*',
|
|
||||||
'site.config.*',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ignores: [
|
|
||||||
'coverage/*',
|
|
||||||
'dist/*',
|
|
||||||
'documentation/*',
|
|
||||||
'node_modules/*',
|
|
||||||
'**/__mocks__/*',
|
|
||||||
'**/__snapshots__/*',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
73
example.env.config.js
Normal file
73
example.env.config.js
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
Learner Dashboard is now able to handle JS-based configuration!
|
||||||
|
|
||||||
|
For the time being, the `.env.*` files are still made available when cloning down this repo or pulling from
|
||||||
|
the master branch. To switch to using `env.config.js`, make a copy of `example.env.config.js` and configure as needed.
|
||||||
|
|
||||||
|
For testing with Jest Snapshot, there is a mock in `/src/setupTest.jsx` for `getConfig` that will need to be
|
||||||
|
uncommented.
|
||||||
|
|
||||||
|
Note: having both .env and env.config.js files will follow a predictable order, in which non-empty values in the
|
||||||
|
JS-based config will overwrite the .env environment variables.
|
||||||
|
|
||||||
|
frontend-platform's getConfig loads configuration in the following sequence:
|
||||||
|
- .env file config
|
||||||
|
- optional handlers (commonly used to merge MFE-specific config in via additional process.env variables)
|
||||||
|
- env.config.js file config
|
||||||
|
- runtime config
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
NODE_ENV: 'development',
|
||||||
|
NODE_PATH: './src',
|
||||||
|
PORT: 1996,
|
||||||
|
BASE_URL: 'localhost:1996',
|
||||||
|
LMS_BASE_URL: 'http://localhost:18000',
|
||||||
|
ECOMMERCE_BASE_URL: 'http://localhost:18130',
|
||||||
|
CREDIT_PURCHASE_URL: 'http://localhost:8140',
|
||||||
|
LOGIN_URL: 'http://localhost:18000/login',
|
||||||
|
LOGOUT_URL: 'http://localhost:18000/logout',
|
||||||
|
LOGO_URL: 'https://edx-cdn.org/v3/default/logo.svg',
|
||||||
|
LOGO_TRADEMARK_URL: 'https://edx-cdn.org/v3/default/logo-trademark.svg',
|
||||||
|
LOGO_WHITE_URL: 'https://edx-cdn.org/v3/default/logo-white.svg',
|
||||||
|
FAVICON_URL: 'https://edx-cdn.org/v3/default/favicon.ico',
|
||||||
|
CSRF_TOKEN_API_PATH: '/csrf/api/v1/token',
|
||||||
|
REFRESH_ACCESS_TOKEN_ENDPOINT: 'http://localhost:18000/login_refresh',
|
||||||
|
ACCESS_TOKEN_COOKIE_NAME: 'edx-jwt-cookie-header-payload',
|
||||||
|
USER_INFO_COOKIE_NAME: 'edx-user-info',
|
||||||
|
SITE_NAME: 'localhost',
|
||||||
|
DATA_API_BASE_URL: 'http://localhost:8000',
|
||||||
|
// LMS_CLIENT_ID should match the lms DOT client application in your LMS container
|
||||||
|
LMS_CLIENT_ID: 'login-service-client-id',
|
||||||
|
SEGMENT_KEY: '',
|
||||||
|
FEATURE_FLAGS: {},
|
||||||
|
MARKETING_SITE_BASE_URL: 'http://localhost:18000',
|
||||||
|
SUPPORT_URL: 'http://localhost:18000/support',
|
||||||
|
CONTACT_URL: 'http://localhost:18000/contact',
|
||||||
|
OPEN_SOURCE_URL: 'http://localhost:18000/openedx',
|
||||||
|
TERMS_OF_SERVICE_URL: 'http://localhost:18000/terms-of-service',
|
||||||
|
PRIVACY_POLICY_URL: 'http://localhost:18000/privacy-policy',
|
||||||
|
FACEBOOK_URL: 'https://www.facebook.com',
|
||||||
|
TWITTER_URL: 'https://twitter.com',
|
||||||
|
YOU_TUBE_URL: 'https://www.youtube.com',
|
||||||
|
LINKED_IN_URL: 'https://www.linkedin.com',
|
||||||
|
REDDIT_URL: 'https://www.reddit.com',
|
||||||
|
APPLE_APP_STORE_URL: 'https://www.apple.com/ios/app-store/',
|
||||||
|
GOOGLE_PLAY_URL: 'https://play.google.com/store',
|
||||||
|
ENTERPRISE_MARKETING_URL: 'http://example.com',
|
||||||
|
ENTERPRISE_MARKETING_UTM_SOURCE: 'example.com',
|
||||||
|
ENTERPRISE_MARKETING_UTM_CAMPAIGN: 'example.com Referral',
|
||||||
|
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM: 'Footer',
|
||||||
|
LEARNING_BASE_URL: 'http://localhost:2000',
|
||||||
|
SESSION_COOKIE_DOMAIN: 'localhost',
|
||||||
|
HOTJAR_APP_ID: '',
|
||||||
|
HOTJAR_VERSION: 6,
|
||||||
|
HOTJAR_DEBUG: '',
|
||||||
|
NEW_RELIC_APP_ID: '',
|
||||||
|
NEW_RELIC_LICENSE_KEY: '',
|
||||||
|
ACCOUNT_SETTINGS_URL: 'http://localhost:1997',
|
||||||
|
ACCOUNT_PROFILE_URL: 'http://localhost:1995',
|
||||||
|
ENABLE_NOTICES: '',
|
||||||
|
CAREER_LINK_URL: '',
|
||||||
|
EXPERIMENT_08_23_VAN_PAINTED_DOOR: true,
|
||||||
|
};
|
||||||
@@ -1,22 +1,18 @@
|
|||||||
const { createConfig } = require('@openedx/frontend-base/tools');
|
const { createConfig } = require('@openedx/frontend-build');
|
||||||
|
|
||||||
module.exports = createConfig('test', {
|
module.exports = createConfig('jest', {
|
||||||
setupFilesAfterEnv: [
|
setupFilesAfterEnv: [
|
||||||
'jest-expect-message',
|
'jest-expect-message',
|
||||||
'<rootDir>/src/setupTest.jsx',
|
'<rootDir>/src/setupTest.jsx',
|
||||||
],
|
],
|
||||||
|
modulePaths: ['<rootDir>/src/'],
|
||||||
coveragePathIgnorePatterns: [
|
coveragePathIgnorePatterns: [
|
||||||
'src/segment.js',
|
'src/segment.js',
|
||||||
'src/postcss.config.js',
|
'src/postcss.config.js',
|
||||||
'testUtils', // don't unit test jest mocking tools
|
'testUtils', // don't unit test jest mocking tools
|
||||||
'src/data/services/lms/fakeData', // don't unit test mock data
|
'src/data/services/lms/fakeData', // don't unit test mock data
|
||||||
'src/test', // don't unit test integration test utils
|
'src/test', // don't unit test integration test utils
|
||||||
'src/__mocks__',
|
|
||||||
],
|
],
|
||||||
moduleNameMapper: {
|
|
||||||
// Asset mocks
|
|
||||||
'\\.svg$': '<rootDir>/src/__mocks__/svg.js',
|
|
||||||
'\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/src/__mocks__/file.js',
|
|
||||||
},
|
|
||||||
testTimeout: 120000,
|
testTimeout: 120000,
|
||||||
|
testEnvironment: 'jsdom',
|
||||||
});
|
});
|
||||||
|
|||||||
10532
package-lock.json
generated
10532
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
86
package.json
Normal file → Executable file
86
package.json
Normal file → Executable file
@@ -1,69 +1,79 @@
|
|||||||
{
|
{
|
||||||
"name": "@openedx/frontend-app-learner-dashboard",
|
"name": "@edx/frontend-app-learner-dashboard",
|
||||||
"version": "1.0.0-alpha.6",
|
"version": "0.0.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/edx/frontend-app-learner-dashboard.git"
|
"url": "git+https://github.com/edx/frontend-app-learner-dashboard.git"
|
||||||
},
|
},
|
||||||
"exports": {
|
|
||||||
".": "./dist/index.js",
|
|
||||||
"./app.scss": "./dist/app.scss"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"/dist"
|
|
||||||
],
|
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"extends @edx/browserslist-config"
|
"extends @edx/browserslist-config"
|
||||||
],
|
],
|
||||||
"sideEffects": [
|
|
||||||
"*.css",
|
|
||||||
"*.scss"
|
|
||||||
],
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "make build",
|
"build": "fedx-scripts webpack",
|
||||||
"clean": "make clean",
|
"i18n_extract": "fedx-scripts formatjs extract",
|
||||||
"dev": "PORT=1996 PUBLIC_PATH=/learner-dashboard openedx dev",
|
"lint": "fedx-scripts eslint --ext .jsx,.js src/",
|
||||||
"i18n_extract": "openedx formatjs extract",
|
"lint-fix": "fedx-scripts eslint --fix --ext .jsx,.js src/",
|
||||||
"lint": "openedx lint .",
|
"semantic-release": "semantic-release",
|
||||||
"lint:fix": "openedx lint --fix .",
|
"start": "fedx-scripts webpack-dev-server --progress",
|
||||||
"prepack": "npm run build",
|
"dev": "PUBLIC_PATH=/learner-dashboard/ MFE_CONFIG_API_URL='http://localhost:8000/api/mfe_config/v1' fedx-scripts webpack-dev-server --progress --host apps.local.openedx.io",
|
||||||
"test": "openedx test --coverage --passWithNoTests"
|
"test": "TZ=GMT fedx-scripts jest --coverage --passWithNoTests",
|
||||||
|
"quality": "npm run lint-fix && npm run test",
|
||||||
|
"watch-tests": "jest --watch",
|
||||||
|
"types": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"author": "Open edX",
|
"author": "edX",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"homepage": "https://github.com/openedx/frontend-app-learner-dashboard#readme",
|
"homepage": "",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/openedx/frontend-app-learner-dashboard/issues"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.3",
|
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
|
||||||
|
"@edx/frontend-component-footer": "^14.6.0",
|
||||||
|
"@edx/frontend-component-header": "^6.6.0",
|
||||||
|
"@edx/frontend-enterprise-hotjar": "7.2.0",
|
||||||
|
"@edx/frontend-platform": "^8.3.1",
|
||||||
"@edx/openedx-atlas": "^0.7.0",
|
"@edx/openedx-atlas": "^0.7.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||||
"@fortawesome/free-brands-svg-icons": "^5.15.4",
|
"@fortawesome/free-brands-svg-icons": "^5.15.4",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
|
"@openedx/frontend-plugin-framework": "^1.7.0",
|
||||||
|
"@openedx/paragon": "^23.4.5",
|
||||||
"@redux-devtools/extension": "3.3.0",
|
"@redux-devtools/extension": "3.3.0",
|
||||||
"@reduxjs/toolkit": "^2.0.0",
|
"@reduxjs/toolkit": "^2.0.0",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
|
"core-js": "3.46.0",
|
||||||
"filesize": "^10.0.0",
|
"filesize": "^10.0.0",
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
|
"history": "5.3.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"prop-types": "15.8.1",
|
"prop-types": "15.8.1",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-helmet": "^6.1.0",
|
||||||
|
"react-intl": "6.8.9",
|
||||||
|
"react-redux": "^7.2.4",
|
||||||
|
"react-router-dom": "6.30.1",
|
||||||
"react-share": "^4.4.0",
|
"react-share": "^4.4.0",
|
||||||
|
"redux": "4.2.1",
|
||||||
"redux-logger": "3.0.6",
|
"redux-logger": "3.0.6",
|
||||||
"redux-thunk": "2.4.2",
|
"redux-thunk": "2.4.2",
|
||||||
"reselect": "^4.0.0"
|
"regenerator-runtime": "^0.14.0",
|
||||||
|
"reselect": "^4.0.0",
|
||||||
|
"universal-cookie": "^4.0.4",
|
||||||
|
"util": "^0.12.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@edx/browserslist-config": "^1.5.0",
|
"@edx/browserslist-config": "^1.3.0",
|
||||||
|
"@edx/typescript-config": "^1.1.0",
|
||||||
|
"@openedx/frontend-build": "^14.6.2",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.2.0",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
|
"copy-webpack-plugin": "^13.0.0",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
@@ -71,20 +81,6 @@
|
|||||||
"jest-when": "^3.6.0",
|
"jest-when": "^3.6.0",
|
||||||
"react-dev-utils": "^12.0.0",
|
"react-dev-utils": "^12.0.0",
|
||||||
"react-test-renderer": "^18.3.1",
|
"react-test-renderer": "^18.3.1",
|
||||||
"redux-mock-store": "^1.5.4",
|
"redux-mock-store": "^1.5.4"
|
||||||
"tsc-alias": "^1.8.16"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@openedx/frontend-base": "^1.0.0-alpha.13",
|
|
||||||
"@openedx/paragon": "^23",
|
|
||||||
"@tanstack/react-query": "^5",
|
|
||||||
"@types/react": "^18",
|
|
||||||
"@types/react-dom": "^18",
|
|
||||||
"react": "^18",
|
|
||||||
"react-dom": "^18",
|
|
||||||
"react-redux": "^8",
|
|
||||||
"react-router": "^6",
|
|
||||||
"react-router-dom": "^6",
|
|
||||||
"redux": "^4"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en-us" dir="ltr">
|
<html lang="en-us" dir="ltr">
|
||||||
<head>
|
<head>
|
||||||
<title>Learner Dashboard Development Site></title>
|
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
2
public/robots.txt
Normal file
2
public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow: /
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { EnvironmentTypes, SiteConfig, footerApp, headerApp, shellApp } from '@openedx/frontend-base';
|
|
||||||
|
|
||||||
import { learnerDashboardApp } from './src';
|
|
||||||
|
|
||||||
import './src/app.scss';
|
|
||||||
|
|
||||||
const siteConfig: SiteConfig = {
|
|
||||||
siteId: 'learner-dashboard-dev',
|
|
||||||
siteName: 'Learner Dashboard Dev',
|
|
||||||
baseUrl: 'http://apps.local.openedx.io:1996',
|
|
||||||
lmsBaseUrl: 'http://local.openedx.io:8000',
|
|
||||||
loginUrl: 'http://local.openedx.io:8000/login',
|
|
||||||
logoutUrl: 'http://local.openedx.io:8000/logout',
|
|
||||||
|
|
||||||
environment: EnvironmentTypes.DEVELOPMENT,
|
|
||||||
apps: [
|
|
||||||
shellApp,
|
|
||||||
headerApp,
|
|
||||||
footerApp,
|
|
||||||
learnerDashboardApp
|
|
||||||
],
|
|
||||||
externalRoutes: [
|
|
||||||
{
|
|
||||||
role: 'org.openedx.frontend.role.profile',
|
|
||||||
url: 'http://apps.local.openedx.io:1995/profile/'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
role: 'org.openedx.frontend.role.account',
|
|
||||||
url: 'http://apps.local.openedx.io:1997/account/'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
role: 'org.openedx.frontend.role.logout',
|
|
||||||
url: 'http://local.openedx.io:8000/logout'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
accessTokenCookieName: 'edx-jwt-cookie-header-payload',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default siteConfig;
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { EnvironmentTypes, SiteConfig } from '@openedx/frontend-base';
|
|
||||||
|
|
||||||
import { appId } from './src/constants';
|
|
||||||
|
|
||||||
const siteConfig: SiteConfig = {
|
|
||||||
siteId: 'learner-dashboard-test-site',
|
|
||||||
siteName: 'Learner Dashboard Test Site',
|
|
||||||
baseUrl: 'http://localhost:1996',
|
|
||||||
lmsBaseUrl: 'http://localhost:8000',
|
|
||||||
loginUrl: 'http://localhost:8000/login',
|
|
||||||
logoutUrl: 'http://localhost:8000/logout',
|
|
||||||
|
|
||||||
environment: EnvironmentTypes.TEST,
|
|
||||||
apps: [{
|
|
||||||
appId,
|
|
||||||
config: {
|
|
||||||
ECOMMERCE_BASE_URL: 'http://localhost:18130',
|
|
||||||
FAVICON_URL: 'https://edx-cdn.org/v3/default/favicon.ico',
|
|
||||||
LEARNING_BASE_URL: 'http://localhost:2000',
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
|
|
||||||
accessTokenCookieName: 'edx-jwt-cookie-header-payload',
|
|
||||||
segmentKey: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default siteConfig;
|
|
||||||
100
src/App.jsx
Executable file
100
src/App.jsx
Executable file
@@ -0,0 +1,100 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Helmet } from 'react-helmet';
|
||||||
|
|
||||||
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
import { logError } from '@edx/frontend-platform/logging';
|
||||||
|
import { initializeHotjar } from '@edx/frontend-enterprise-hotjar';
|
||||||
|
|
||||||
|
import { ErrorPage, AppContext } from '@edx/frontend-platform/react';
|
||||||
|
import { FooterSlot } from '@edx/frontend-component-footer';
|
||||||
|
import { Alert } from '@openedx/paragon';
|
||||||
|
|
||||||
|
import { RequestKeys } from 'data/constants/requests';
|
||||||
|
import store from 'data/store';
|
||||||
|
import {
|
||||||
|
selectors,
|
||||||
|
actions,
|
||||||
|
} from 'data/redux';
|
||||||
|
import { reduxHooks } from 'hooks';
|
||||||
|
import Dashboard from 'containers/Dashboard';
|
||||||
|
|
||||||
|
import track from 'tracking';
|
||||||
|
|
||||||
|
import fakeData from 'data/services/lms/fakeData/courses';
|
||||||
|
|
||||||
|
import AppWrapper from 'containers/AppWrapper';
|
||||||
|
import LearnerDashboardHeader from 'containers/LearnerDashboardHeader';
|
||||||
|
|
||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import messages from './messages';
|
||||||
|
import './App.scss';
|
||||||
|
|
||||||
|
export const App = () => {
|
||||||
|
const { authenticatedUser } = React.useContext(AppContext);
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const isFailed = {
|
||||||
|
initialize: reduxHooks.useRequestIsFailed(RequestKeys.initialize),
|
||||||
|
refreshList: reduxHooks.useRequestIsFailed(RequestKeys.refreshList),
|
||||||
|
};
|
||||||
|
const hasNetworkFailure = isFailed.initialize || isFailed.refreshList;
|
||||||
|
const { supportEmail } = reduxHooks.usePlatformSettingsData();
|
||||||
|
const loadData = reduxHooks.useLoadData();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (authenticatedUser?.administrator || getConfig().NODE_ENV === 'development') {
|
||||||
|
window.loadEmptyData = () => {
|
||||||
|
loadData({ ...fakeData.globalData, courses: [] });
|
||||||
|
};
|
||||||
|
window.loadMockData = () => {
|
||||||
|
loadData({
|
||||||
|
...fakeData.globalData,
|
||||||
|
courses: [
|
||||||
|
...fakeData.courseRunData,
|
||||||
|
...fakeData.entitlementData,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
window.store = store;
|
||||||
|
window.selectors = selectors;
|
||||||
|
window.actions = actions;
|
||||||
|
window.track = track;
|
||||||
|
}
|
||||||
|
if (getConfig().HOTJAR_APP_ID) {
|
||||||
|
try {
|
||||||
|
initializeHotjar({
|
||||||
|
hotjarId: getConfig().HOTJAR_APP_ID,
|
||||||
|
hotjarVersion: getConfig().HOTJAR_VERSION,
|
||||||
|
hotjarDebug: !!getConfig().HOTJAR_DEBUG,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [authenticatedUser, loadData]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet>
|
||||||
|
<title>{formatMessage(messages.pageTitle)}</title>
|
||||||
|
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
|
||||||
|
</Helmet>
|
||||||
|
<div>
|
||||||
|
<AppWrapper>
|
||||||
|
<LearnerDashboardHeader />
|
||||||
|
<main id="main">
|
||||||
|
{hasNetworkFailure
|
||||||
|
? (
|
||||||
|
<Alert variant="danger">
|
||||||
|
<ErrorPage message={formatMessage(messages.errorMessage, { supportEmail })} />
|
||||||
|
</Alert>
|
||||||
|
) : (
|
||||||
|
<Dashboard />
|
||||||
|
)}
|
||||||
|
</main>
|
||||||
|
</AppWrapper>
|
||||||
|
<FooterSlot />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
66
src/App.scss
Executable file
66
src/App.scss
Executable file
@@ -0,0 +1,66 @@
|
|||||||
|
// frontend-app-*/src/index.scss
|
||||||
|
@use "@openedx/paragon/styles/css/core/custom-media-breakpoints" as paragonCustomMediaBreakpoints;
|
||||||
|
|
||||||
|
$fa-font-path: "~font-awesome/fonts";
|
||||||
|
@import "~font-awesome/scss/font-awesome";
|
||||||
|
|
||||||
|
$input-focus-box-shadow: var(--pgn-elevation-form-input-base); // hack to get upgrade to paragon 4.0.0 to work
|
||||||
|
|
||||||
|
@import "~@edx/frontend-component-header/dist/index";
|
||||||
|
@import "~@edx/frontend-component-footer/dist/_footer";
|
||||||
|
|
||||||
|
.text-ellipsis {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert.alert-info .alert-icon {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
// Removing a odd 1.5 scaling on checkboxes.:
|
||||||
|
input[type=checkbox] {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
|
||||||
|
main {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: block;
|
||||||
|
box-sizing: content-box;
|
||||||
|
position: relative;
|
||||||
|
top: 0.1em;
|
||||||
|
height: 1.75rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
flex: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#paragon-portal-root {
|
||||||
|
.pgn__modal-layer {
|
||||||
|
.pgn__modal-close-container {
|
||||||
|
right: 1rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.confirm-modal .pgn__modal-body {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
135
src/App.test.jsx
Normal file
135
src/App.test.jsx
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
|
||||||
|
import { RequestKeys } from 'data/constants/requests';
|
||||||
|
import { reduxHooks } from 'hooks';
|
||||||
|
import { App } from './App';
|
||||||
|
import messages from './messages';
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-component-footer', () => ({
|
||||||
|
FooterSlot: jest.fn(() => <div>FooterSlot</div>),
|
||||||
|
}));
|
||||||
|
jest.mock('containers/Dashboard', () => jest.fn(() => <div>Dashboard</div>));
|
||||||
|
jest.mock('containers/LearnerDashboardHeader', () => jest.fn(() => <div>LearnerDashboardHeader</div>));
|
||||||
|
jest.mock('containers/AppWrapper', () => jest.fn(({ children }) => <div className="AppWrapper">{children}</div>));
|
||||||
|
jest.mock('data/redux', () => ({
|
||||||
|
selectors: 'redux.selectors',
|
||||||
|
actions: 'redux.actions',
|
||||||
|
thunkActions: 'redux.thunkActions',
|
||||||
|
}));
|
||||||
|
jest.mock('hooks', () => ({
|
||||||
|
reduxHooks: {
|
||||||
|
useRequestIsFailed: jest.fn(),
|
||||||
|
usePlatformSettingsData: jest.fn(),
|
||||||
|
useLoadData: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
jest.mock('data/store', () => 'data/store');
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform', () => ({
|
||||||
|
getConfig: jest.fn(() => ({})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform/react', () => ({
|
||||||
|
...jest.requireActual('@edx/frontend-platform/react'),
|
||||||
|
ErrorPage: () => 'ErrorPage',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const loadData = jest.fn();
|
||||||
|
reduxHooks.useLoadData.mockReturnValue(loadData);
|
||||||
|
|
||||||
|
const supportEmail = 'test@support.com';
|
||||||
|
reduxHooks.usePlatformSettingsData.mockReturnValue({ supportEmail });
|
||||||
|
|
||||||
|
describe('App router component', () => {
|
||||||
|
describe('component', () => {
|
||||||
|
const runBasicTests = () => {
|
||||||
|
it('displays title in helmet component', async () => {
|
||||||
|
await waitFor(() => expect(document.title).toEqual(messages.pageTitle.defaultMessage));
|
||||||
|
});
|
||||||
|
it('displays learner dashboard header', () => {
|
||||||
|
const learnerDashboardHeader = screen.getByText('LearnerDashboardHeader');
|
||||||
|
expect(learnerDashboardHeader).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('wraps the header and main components in an AppWrapper widget container', () => {
|
||||||
|
const appWrapper = screen.getByText('LearnerDashboardHeader').parentElement;
|
||||||
|
expect(appWrapper).toHaveClass('AppWrapper');
|
||||||
|
expect(appWrapper.children[1].id).toEqual('main');
|
||||||
|
});
|
||||||
|
it('displays footer slot', () => {
|
||||||
|
const footerSlot = screen.getByText('FooterSlot');
|
||||||
|
expect(footerSlot).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
describe('no network failure', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
reduxHooks.useRequestIsFailed.mockReturnValue(false);
|
||||||
|
getConfig.mockReturnValue({});
|
||||||
|
render(<IntlProvider locale="en"><App /></IntlProvider>);
|
||||||
|
});
|
||||||
|
runBasicTests();
|
||||||
|
it('loads dashboard', () => {
|
||||||
|
const dashboard = screen.getByText('Dashboard');
|
||||||
|
expect(dashboard).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('no network failure with optimizely url', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
reduxHooks.useRequestIsFailed.mockReturnValue(false);
|
||||||
|
getConfig.mockReturnValue({ OPTIMIZELY_URL: 'fake.url' });
|
||||||
|
render(<IntlProvider locale="en"><App /></IntlProvider>);
|
||||||
|
});
|
||||||
|
runBasicTests();
|
||||||
|
it('loads dashboard', () => {
|
||||||
|
const dashboard = screen.getByText('Dashboard');
|
||||||
|
expect(dashboard).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('no network failure with optimizely project id', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
reduxHooks.useRequestIsFailed.mockReturnValue(false);
|
||||||
|
getConfig.mockReturnValue({ OPTIMIZELY_PROJECT_ID: 'fakeId' });
|
||||||
|
render(<IntlProvider locale="en"><App /></IntlProvider>);
|
||||||
|
});
|
||||||
|
runBasicTests();
|
||||||
|
it('loads dashboard', () => {
|
||||||
|
const dashboard = screen.getByText('Dashboard');
|
||||||
|
expect(dashboard).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('initialize failure', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
reduxHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.initialize);
|
||||||
|
getConfig.mockReturnValue({});
|
||||||
|
render(<IntlProvider locale="en" messages={messages}><App /></IntlProvider>);
|
||||||
|
});
|
||||||
|
runBasicTests();
|
||||||
|
it('loads error page', () => {
|
||||||
|
const alert = screen.getByRole('alert');
|
||||||
|
expect(alert).toBeInTheDocument();
|
||||||
|
const errorPage = screen.getByText('ErrorPage');
|
||||||
|
expect(errorPage).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('refresh failure', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
reduxHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.refreshList);
|
||||||
|
getConfig.mockReturnValue({});
|
||||||
|
render(<IntlProvider locale="en"><App /></IntlProvider>);
|
||||||
|
});
|
||||||
|
runBasicTests();
|
||||||
|
it('loads error page', () => {
|
||||||
|
const alert = screen.getByRole('alert');
|
||||||
|
expect(alert).toBeInTheDocument();
|
||||||
|
const errorPage = screen.getByText('ErrorPage');
|
||||||
|
expect(errorPage).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
20
src/Main.jsx
20
src/Main.jsx
@@ -1,20 +0,0 @@
|
|||||||
import { Provider as ReduxProvider } from 'react-redux';
|
|
||||||
import { CurrentAppProvider, PageWrap } from '@openedx/frontend-base';
|
|
||||||
|
|
||||||
import { appId } from './constants';
|
|
||||||
import store from './data/store';
|
|
||||||
import Dashboard from './containers/Dashboard';
|
|
||||||
|
|
||||||
import './app.scss';
|
|
||||||
|
|
||||||
const Main = () => (
|
|
||||||
<CurrentAppProvider appId={appId}>
|
|
||||||
<ReduxProvider store={store}>
|
|
||||||
<PageWrap>
|
|
||||||
<Dashboard />
|
|
||||||
</PageWrap>
|
|
||||||
</ReduxProvider>
|
|
||||||
</CurrentAppProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default Main;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
module.exports = 'FileMock';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
module.exports = 'SvgURL';
|
|
||||||
38
src/app.scss
38
src/app.scss
@@ -1,38 +0,0 @@
|
|||||||
@use "@openedx/frontend-base/shell/app.scss";
|
|
||||||
|
|
||||||
$fa-font-path: "~font-awesome/fonts";
|
|
||||||
@import "~font-awesome/scss/font-awesome";
|
|
||||||
|
|
||||||
$input-focus-box-shadow: var(--pgn-elevation-form-input-base); // hack to get upgrade to paragon 4.0.0 to work
|
|
||||||
|
|
||||||
#learnerDashboardRoot {
|
|
||||||
main {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Removing a odd 1.5 scaling on checkboxes.:
|
|
||||||
input[type=checkbox] {
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-ellipsis {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert.alert-info .alert-icon {
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
#paragon-portal-root {
|
|
||||||
.pgn__modal-layer {
|
|
||||||
.pgn__modal-close-container {
|
|
||||||
right: 1rem !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.confirm-modal .pgn__modal-body {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
23
src/app.ts
23
src/app.ts
@@ -1,23 +0,0 @@
|
|||||||
import { App } from '@openedx/frontend-base';
|
|
||||||
import { appId } from './constants';
|
|
||||||
import routes from './routes';
|
|
||||||
import providers from './providers';
|
|
||||||
import messages from './i18n';
|
|
||||||
import slots from './slots';
|
|
||||||
|
|
||||||
const app: App = {
|
|
||||||
appId,
|
|
||||||
routes,
|
|
||||||
providers,
|
|
||||||
messages,
|
|
||||||
slots,
|
|
||||||
config: {
|
|
||||||
LEARNING_BASE_URL: 'http://apps.local.openedx.io:2000',
|
|
||||||
ENABLE_PROGRAMS: false,
|
|
||||||
ECOMMERCE_BASE_URL: '',
|
|
||||||
ORDER_HISTORY_URL: '',
|
|
||||||
SUPPORT_URL: '',
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default app;
|
|
||||||
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
15
src/assets/top_stripe.svg
Normal file
15
src/assets/top_stripe.svg
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<svg width="1350" height="7" viewBox="0 0 1350 7" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_3302_26079)">
|
||||||
|
<rect width="1350" height="6.75" transform="translate(0 -0.375)" fill="#03C7E8"/>
|
||||||
|
<rect y="-0.375" width="585.562" height="6.75" fill="#D23228"/>
|
||||||
|
<path d="M549.281 -0.375H933.188L929.491 6.375H549.281V-0.375Z" fill="#002121"/>
|
||||||
|
<path d="M550.129 13.125L545.062 -10.5L555.188 -10.5L550.129 13.125Z" fill="#D23228"/>
|
||||||
|
<path d="M931.082 13.125L925.594 -6.28125L936.563 -6.28125L931.082 13.125Z" fill="#002121"/>
|
||||||
|
<path d="M0 -0.375H106.312L105.289 6.375H0V-0.375Z" fill="#921108"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_3302_26079">
|
||||||
|
<rect width="1350" height="6.75" fill="white" transform="translate(0 -0.375)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 765 B |
25
src/components/NoticesWrapper/api.js
Normal file
25
src/components/NoticesWrapper/api.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||||
|
import { logError, logInfo } from '@edx/frontend-platform/logging';
|
||||||
|
|
||||||
|
export const noticesUrl = `${getConfig().LMS_BASE_URL}/notices/api/v1/unacknowledged`;
|
||||||
|
|
||||||
|
export const getNotices = ({ onLoad, notFoundMessage }) => {
|
||||||
|
const authenticatedUser = getAuthenticatedUser();
|
||||||
|
|
||||||
|
const handleError = async (e) => {
|
||||||
|
// Error probably means that notices is not installed, which is fine.
|
||||||
|
const { customAttributes: { httpErrorStatus } } = e;
|
||||||
|
if (httpErrorStatus === 404) {
|
||||||
|
logInfo(`${e}. ${notFoundMessage}`);
|
||||||
|
} else {
|
||||||
|
logError(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (authenticatedUser) {
|
||||||
|
return getAuthenticatedHttpClient().get(noticesUrl, {}).then(onLoad).catch(handleError);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default { getNotices };
|
||||||
65
src/components/NoticesWrapper/api.test.js
Normal file
65
src/components/NoticesWrapper/api.test.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||||
|
import { logError, logInfo } from '@edx/frontend-platform/logging';
|
||||||
|
|
||||||
|
import * as api from './api';
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform', () => ({
|
||||||
|
getConfig: jest.fn(() => ({
|
||||||
|
LMS_BASE_URL: 'test-lms-url',
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform/auth', () => ({
|
||||||
|
getAuthenticatedHttpClient: jest.fn(),
|
||||||
|
getAuthenticatedUser: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform/logging', () => ({
|
||||||
|
logError: jest.fn(),
|
||||||
|
logInfo: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const testData = 'test-data';
|
||||||
|
const successfulGet = () => Promise.resolve(testData);
|
||||||
|
const error404 = { customAttributes: { httpErrorStatus: 404 }, test: 'error' };
|
||||||
|
const error404Get = () => Promise.reject(error404);
|
||||||
|
const error500 = { customAttributes: { httpErrorStatus: 500 }, test: 'error' };
|
||||||
|
const error500Get = () => Promise.reject(error500);
|
||||||
|
|
||||||
|
const get = jest.fn().mockImplementation(successfulGet);
|
||||||
|
getAuthenticatedHttpClient.mockReturnValue({ get });
|
||||||
|
const authenticatedUser = { fake: 'user' };
|
||||||
|
getAuthenticatedUser.mockReturnValue(authenticatedUser);
|
||||||
|
|
||||||
|
const onLoad = jest.fn();
|
||||||
|
describe('getNotices api method', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
describe('behavior', () => {
|
||||||
|
describe('not authenticated', () => {
|
||||||
|
it('does not fetch anything', () => {
|
||||||
|
getAuthenticatedUser.mockReturnValueOnce(null);
|
||||||
|
api.getNotices({ onLoad });
|
||||||
|
expect(get).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('authenticated', () => {
|
||||||
|
it('fetches noticesUrl with onLoad behavior', async () => {
|
||||||
|
await api.getNotices({ onLoad });
|
||||||
|
expect(get).toHaveBeenCalledWith(api.noticesUrl, {});
|
||||||
|
expect(onLoad).toHaveBeenCalledWith(testData);
|
||||||
|
});
|
||||||
|
it('calls logInfo if fetch fails with 404', async () => {
|
||||||
|
get.mockImplementation(error404Get);
|
||||||
|
await api.getNotices({ onLoad });
|
||||||
|
expect(logInfo).toHaveBeenCalledWith(`${error404}. ${api.error404Message}`);
|
||||||
|
});
|
||||||
|
it('calls logError if fetch fails with non-404 error', async () => {
|
||||||
|
get.mockImplementation(error500Get);
|
||||||
|
await api.getNotices({ onLoad });
|
||||||
|
expect(logError).toHaveBeenCalledWith(error500);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
40
src/components/NoticesWrapper/hooks.js
Normal file
40
src/components/NoticesWrapper/hooks.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
import { StrictDict } from 'utils';
|
||||||
|
import { getNotices } from './api';
|
||||||
|
import * as module from './hooks';
|
||||||
|
import messages from './messages';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component uses the platform-plugin-notices plugin to function.
|
||||||
|
* If the user has an unacknowledged notice, they will be rerouted off
|
||||||
|
* course home and onto a full-screen notice page. If the plugin is not
|
||||||
|
* installed, or there are no notices, we just passthrough this component.
|
||||||
|
*/
|
||||||
|
export const state = StrictDict({
|
||||||
|
isRedirected: (val) => React.useState(val), // eslint-disable-line
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useNoticesWrapperData = () => {
|
||||||
|
const [isRedirected, setIsRedirected] = module.state.isRedirected();
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (getConfig().ENABLE_NOTICES) {
|
||||||
|
getNotices({
|
||||||
|
onLoad: (data) => {
|
||||||
|
if (data?.data?.results?.length > 0) {
|
||||||
|
setIsRedirected(true);
|
||||||
|
window.location.replace(`${data.data.results[0]}?next=${window.location.href}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
notFoundMessage: formatMessage(messages.error404Message),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [setIsRedirected, formatMessage]);
|
||||||
|
return { isRedirected };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useNoticesWrapperData;
|
||||||
99
src/components/NoticesWrapper/hooks.test.js
Normal file
99
src/components/NoticesWrapper/hooks.test.js
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { MockUseState, formatMessage } from 'testUtils';
|
||||||
|
|
||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import { getNotices } from './api';
|
||||||
|
import * as hooks from './hooks';
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform', () => ({ getConfig: jest.fn() }));
|
||||||
|
jest.mock('./api', () => ({ getNotices: jest.fn() }));
|
||||||
|
|
||||||
|
jest.mock('react', () => ({
|
||||||
|
...jest.requireActual('react'),
|
||||||
|
useEffect: jest.fn((cb, prereqs) => ({ useEffect: { cb, prereqs } })),
|
||||||
|
useContext: jest.fn(context => context),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform/i18n', () => {
|
||||||
|
const { formatMessage: fn } = jest.requireActual('testUtils');
|
||||||
|
return {
|
||||||
|
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||||
|
useIntl: () => ({
|
||||||
|
formatMessage: fn,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
getConfig.mockReturnValue({ ENABLE_NOTICES: true });
|
||||||
|
const state = new MockUseState(hooks);
|
||||||
|
|
||||||
|
let hook;
|
||||||
|
describe('NoticesWrapper hooks', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
describe('state hooks', () => {
|
||||||
|
state.testGetter(state.keys.isRedirected);
|
||||||
|
});
|
||||||
|
describe('useNoticesWrapperData', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
state.mock();
|
||||||
|
});
|
||||||
|
describe('behavior', () => {
|
||||||
|
it('initializes state hooks', () => {
|
||||||
|
hooks.useNoticesWrapperData();
|
||||||
|
expect(hooks.state.isRedirected).toHaveBeenCalledWith();
|
||||||
|
});
|
||||||
|
describe('effects', () => {
|
||||||
|
it('does not call notices if not enabled', () => {
|
||||||
|
getConfig.mockReturnValueOnce({ ENABLE_NOTICES: false });
|
||||||
|
hooks.useNoticesWrapperData();
|
||||||
|
const [cb, prereqs] = React.useEffect.mock.calls[0];
|
||||||
|
expect(prereqs).toEqual([state.setState.isRedirected, formatMessage]);
|
||||||
|
cb();
|
||||||
|
expect(getNotices).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
describe('getNotices call (if enabled) onLoad behavior', () => {
|
||||||
|
it('does not redirect if there are no results', () => {
|
||||||
|
hooks.useNoticesWrapperData();
|
||||||
|
expect(React.useEffect).toHaveBeenCalled();
|
||||||
|
const [cb, prereqs] = React.useEffect.mock.calls[0];
|
||||||
|
expect(prereqs).toEqual([state.setState.isRedirected, formatMessage]);
|
||||||
|
cb();
|
||||||
|
expect(getNotices).toHaveBeenCalled();
|
||||||
|
const { onLoad } = getNotices.mock.calls[0][0];
|
||||||
|
onLoad({});
|
||||||
|
expect(state.setState.isRedirected).not.toHaveBeenCalled();
|
||||||
|
onLoad({ data: {} });
|
||||||
|
expect(state.setState.isRedirected).not.toHaveBeenCalled();
|
||||||
|
onLoad({ data: { results: [] } });
|
||||||
|
expect(state.setState.isRedirected).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
it('redirects and set isRedirected if results are returned', () => {
|
||||||
|
delete window.location;
|
||||||
|
window.location = { replace: jest.fn(), href: 'test-old-href' };
|
||||||
|
hooks.useNoticesWrapperData();
|
||||||
|
const [cb, prereqs] = React.useEffect.mock.calls[0];
|
||||||
|
expect(prereqs).toEqual([state.setState.isRedirected, formatMessage]);
|
||||||
|
cb();
|
||||||
|
expect(getNotices).toHaveBeenCalled();
|
||||||
|
const { onLoad } = getNotices.mock.calls[0][0];
|
||||||
|
const target = 'url-target';
|
||||||
|
onLoad({ data: { results: [target] } });
|
||||||
|
expect(state.setState.isRedirected).toHaveBeenCalledWith(true);
|
||||||
|
expect(window.location.replace).toHaveBeenCalledWith(
|
||||||
|
`${target}?next=${window.location.href}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('output', () => {
|
||||||
|
it('forwards isRedirected from state call', () => {
|
||||||
|
hook = hooks.useNoticesWrapperData();
|
||||||
|
expect(hook.isRedirected).toEqual(state.stateVals.isRedirected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
25
src/components/NoticesWrapper/index.jsx
Normal file
25
src/components/NoticesWrapper/index.jsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import useNoticesWrapperData from './hooks';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component uses the platform-plugin-notices plugin to function.
|
||||||
|
* If the user has an unacknowledged notice, they will be rerouted off
|
||||||
|
* course home and onto a full-screen notice page. If the plugin is not
|
||||||
|
* installed, or there are no notices, we just passthrough this component.
|
||||||
|
*/
|
||||||
|
const NoticesWrapper = ({ children }) => {
|
||||||
|
const { isRedirected } = useNoticesWrapperData();
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isRedirected === true ? null : children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
NoticesWrapper.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NoticesWrapper;
|
||||||
36
src/components/NoticesWrapper/index.test.jsx
Normal file
36
src/components/NoticesWrapper/index.test.jsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
|
||||||
|
import useNoticesWrapperData from './hooks';
|
||||||
|
import NoticesWrapper from '.';
|
||||||
|
|
||||||
|
jest.mock('./hooks', () => jest.fn());
|
||||||
|
|
||||||
|
const hookProps = { isRedirected: false };
|
||||||
|
|
||||||
|
const children = [<b key={1}>some</b>, <i key={2}>children</i>];
|
||||||
|
describe('NoticesWrapper component', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
useNoticesWrapperData.mockClear();
|
||||||
|
});
|
||||||
|
describe('behavior', () => {
|
||||||
|
it('initializes hooks', () => {
|
||||||
|
useNoticesWrapperData.mockReturnValue(hookProps);
|
||||||
|
render(<NoticesWrapper>{children}</NoticesWrapper>);
|
||||||
|
expect(useNoticesWrapperData).toHaveBeenCalledWith();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('output', () => {
|
||||||
|
it('does not show children if redirected', () => {
|
||||||
|
useNoticesWrapperData.mockReturnValueOnce({ isRedirected: true });
|
||||||
|
render(<NoticesWrapper>{children}</NoticesWrapper>);
|
||||||
|
expect(screen.queryByText('some')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('children')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('shows children if not redirected', () => {
|
||||||
|
useNoticesWrapperData.mockReturnValue(hookProps);
|
||||||
|
render(<NoticesWrapper>{children}</NoticesWrapper>);
|
||||||
|
expect(screen.getByText('some')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('children')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
11
src/components/NoticesWrapper/messages.js
Normal file
11
src/components/NoticesWrapper/messages.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
error404Message: {
|
||||||
|
id: 'learner-dash.notices.error404Message',
|
||||||
|
defaultMessage: 'This probably happened because the notices plugin is not installed on platform.',
|
||||||
|
description: 'Error message when notices API returns 404',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default messages;
|
||||||
28
src/config/index.js
Normal file
28
src/config/index.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
const configuration = {
|
||||||
|
// BASE_URL: process.env.BASE_URL,
|
||||||
|
LMS_BASE_URL: process.env.LMS_BASE_URL,
|
||||||
|
ECOMMERCE_BASE_URL: process.env.ECOMMERCE_BASE_URL,
|
||||||
|
CREDIT_PURCHASE_URL: process.env.CREDIT_PURCHASE_URL,
|
||||||
|
// LOGIN_URL: process.env.LOGIN_URL,
|
||||||
|
// LOGOUT_URL: process.env.LOGOUT_URL,
|
||||||
|
// CSRF_TOKEN_API_PATH: process.env.CSRF_TOKEN_API_PATH,
|
||||||
|
// REFRESH_ACCESS_TOKEN_ENDPOINT: process.env.REFRESH_ACCESS_TOKEN_ENDPOINT,
|
||||||
|
// DATA_API_BASE_URL: process.env.DATA_API_BASE_URL,
|
||||||
|
// SECURE_COOKIES: process.env.NODE_ENV !== 'development',
|
||||||
|
SEGMENT_KEY: process.env.SEGMENT_KEY,
|
||||||
|
// ACCESS_TOKEN_COOKIE_NAME: process.env.ACCESS_TOKEN_COOKIE_NAME,
|
||||||
|
LEARNING_BASE_URL: process.env.LEARNING_BASE_URL,
|
||||||
|
SESSION_COOKIE_DOMAIN: process.env.SESSION_COOKIE_DOMAIN || '',
|
||||||
|
SUPPORT_URL: process.env.SUPPORT_URL || null,
|
||||||
|
ENABLE_NOTICES: process.env.ENABLE_NOTICES || null,
|
||||||
|
CAREER_LINK_URL: process.env.CAREER_LINK_URL || null,
|
||||||
|
LOGO_URL: process.env.LOGO_URL,
|
||||||
|
ENABLE_EDX_PERSONAL_DASHBOARD: process.env.ENABLE_EDX_PERSONAL_DASHBOARD === 'true',
|
||||||
|
SEARCH_CATALOG_URL: process.env.SEARCH_CATALOG_URL || null,
|
||||||
|
ENABLE_PROGRAMS: process.env.ENABLE_PROGRAMS === 'true',
|
||||||
|
NON_BROWSABLE_COURSES: process.env.NON_BROWSABLE_COURSES === 'true',
|
||||||
|
};
|
||||||
|
|
||||||
|
const features = {};
|
||||||
|
|
||||||
|
export { configuration, features };
|
||||||
@@ -1 +0,0 @@
|
|||||||
export const appId = 'org.openedx.frontend.app.learnerDashboard';
|
|
||||||
13
src/containers/AppWrapper/index.jsx
Normal file
13
src/containers/AppWrapper/index.jsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
export const AppWrapper = ({
|
||||||
|
children,
|
||||||
|
}) => children;
|
||||||
|
AppWrapper.propTypes = {
|
||||||
|
children: PropTypes.oneOfType([
|
||||||
|
PropTypes.node,
|
||||||
|
PropTypes.arrayOf(PropTypes.node),
|
||||||
|
]).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppWrapper;
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@openedx/frontend-base';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import track from '../../../../tracking';
|
|
||||||
import { reduxHooks } from '../../../../hooks';
|
|
||||||
|
|
||||||
|
import track from 'tracking';
|
||||||
|
import { reduxHooks } from 'hooks';
|
||||||
import useActionDisabledState from '../hooks';
|
import useActionDisabledState from '../hooks';
|
||||||
import ActionButton from './ActionButton';
|
import ActionButton from './ActionButton';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { IntlProvider } from '@openedx/frontend-base';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
import { reduxHooks } from '@src/hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import track from '@src/tracking';
|
import track from 'tracking';
|
||||||
import useActionDisabledState from '../hooks';
|
import useActionDisabledState from '../hooks';
|
||||||
import BeginCourseButton from './BeginCourseButton';
|
import BeginCourseButton from './BeginCourseButton';
|
||||||
|
|
||||||
jest.mock('@src/tracking', () => ({
|
jest.mock('tracking', () => ({
|
||||||
course: {
|
course: {
|
||||||
enterCourseClicked: jest.fn().mockName('segment.enterCourseClicked'),
|
enterCourseClicked: jest.fn().mockName('segment.enterCourseClicked'),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
useCardCourseRunData: jest.fn(),
|
useCardCourseRunData: jest.fn(),
|
||||||
useCardExecEdTrackingParam: jest.fn(),
|
useCardExecEdTrackingParam: jest.fn(),
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@openedx/frontend-base';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import track from '../../../../tracking';
|
|
||||||
import { reduxHooks } from '../../../../hooks';
|
|
||||||
|
|
||||||
|
import track from 'tracking';
|
||||||
|
import { reduxHooks } from 'hooks';
|
||||||
import useActionDisabledState from '../hooks';
|
import useActionDisabledState from '../hooks';
|
||||||
import ActionButton from './ActionButton';
|
import ActionButton from './ActionButton';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { IntlProvider } from '@openedx/frontend-base';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { reduxHooks } from '@src/hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import track from '@src/tracking';
|
import track from 'tracking';
|
||||||
import useActionDisabledState from '../hooks';
|
import useActionDisabledState from '../hooks';
|
||||||
import ResumeButton from './ResumeButton';
|
import ResumeButton from './ResumeButton';
|
||||||
|
|
||||||
jest.mock('@src/tracking', () => ({
|
jest.mock('tracking', () => ({
|
||||||
course: {
|
course: {
|
||||||
enterCourseClicked: jest.fn().mockName('segment.enterCourseClicked'),
|
enterCourseClicked: jest.fn().mockName('segment.enterCourseClicked'),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
useCardCourseRunData: jest.fn(),
|
useCardCourseRunData: jest.fn(),
|
||||||
useCardExecEdTrackingParam: jest.fn(),
|
useCardExecEdTrackingParam: jest.fn(),
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@openedx/frontend-base';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { reduxHooks } from '../../../../hooks';
|
|
||||||
|
|
||||||
|
import { reduxHooks } from 'hooks';
|
||||||
import useActionDisabledState from '../hooks';
|
import useActionDisabledState from '../hooks';
|
||||||
import ActionButton from './ActionButton';
|
import ActionButton from './ActionButton';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { IntlProvider } from '@openedx/frontend-base';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { reduxHooks } from '@src/hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import useActionDisabledState from '../hooks';
|
import useActionDisabledState from '../hooks';
|
||||||
|
|
||||||
import SelectSessionButton from './SelectSessionButton';
|
import SelectSessionButton from './SelectSessionButton';
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
useUpdateSelectSessionModalCallback: jest.fn(),
|
useUpdateSelectSessionModalCallback: jest.fn(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@openedx/frontend-base';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import track from '../../../../tracking';
|
|
||||||
import { reduxHooks } from '../../../../hooks';
|
|
||||||
|
|
||||||
|
import track from 'tracking';
|
||||||
|
import { reduxHooks } from 'hooks';
|
||||||
import useActionDisabledState from '../hooks';
|
import useActionDisabledState from '../hooks';
|
||||||
import ActionButton from './ActionButton';
|
import ActionButton from './ActionButton';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { IntlProvider } from '@openedx/frontend-base';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import track from '@src/tracking';
|
import track from 'tracking';
|
||||||
import { reduxHooks } from '@src/hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import useActionDisabledState from '../hooks';
|
import useActionDisabledState from '../hooks';
|
||||||
import ViewCourseButton from './ViewCourseButton';
|
import ViewCourseButton from './ViewCourseButton';
|
||||||
|
|
||||||
jest.mock('@src/tracking', () => ({
|
jest.mock('tracking', () => ({
|
||||||
course: {
|
course: {
|
||||||
enterCourseClicked: jest.fn().mockName('segment.enterCourseClicked'),
|
enterCourseClicked: jest.fn().mockName('segment.enterCourseClicked'),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
useCardCourseRunData: jest.fn(() => ({ homeUrl: 'homeUrl' })),
|
useCardCourseRunData: jest.fn(() => ({ homeUrl: 'homeUrl' })),
|
||||||
useTrackCourseEvent: jest.fn(),
|
useTrackCourseEvent: jest.fn(),
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
import { ActionRow } from '@openedx/paragon';
|
import { ActionRow } from '@openedx/paragon';
|
||||||
|
|
||||||
import { reduxHooks } from '../../../../hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import CourseCardActionSlot from '../../../../slots/CourseCardActionSlot';
|
|
||||||
|
|
||||||
|
import CourseCardActionSlot from 'plugin-slots/CourseCardActionSlot';
|
||||||
import SelectSessionButton from './SelectSessionButton';
|
import SelectSessionButton from './SelectSessionButton';
|
||||||
import BeginCourseButton from './BeginCourseButton';
|
import BeginCourseButton from './BeginCourseButton';
|
||||||
import ResumeButton from './ResumeButton';
|
import ResumeButton from './ResumeButton';
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { reduxHooks } from '@src/hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
|
|
||||||
import CourseCardActions from '.';
|
import CourseCardActions from '.';
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
useCardCourseRunData: jest.fn(),
|
useCardCourseRunData: jest.fn(),
|
||||||
useCardEnrollmentData: jest.fn(),
|
useCardEnrollmentData: jest.fn(),
|
||||||
@@ -12,7 +12,7 @@ jest.mock('@src/hooks', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('@src/slots/CourseCardActionSlot', () => jest.fn(() => <div>CourseCardActionSlot</div>));
|
jest.mock('plugin-slots/CourseCardActionSlot', () => jest.fn(() => <div>CourseCardActionSlot</div>));
|
||||||
jest.mock('./SelectSessionButton', () => jest.fn(() => <div>SelectSessionButton</div>));
|
jest.mock('./SelectSessionButton', () => jest.fn(() => <div>SelectSessionButton</div>));
|
||||||
jest.mock('./ViewCourseButton', () => jest.fn(() => <div>ViewCourseButton</div>));
|
jest.mock('./ViewCourseButton', () => jest.fn(() => <div>ViewCourseButton</div>));
|
||||||
jest.mock('./BeginCourseButton', () => jest.fn(() => <div>BeginCourseButton</div>));
|
jest.mock('./BeginCourseButton', () => jest.fn(() => <div>BeginCourseButton</div>));
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineMessages } from '@openedx/frontend-base';
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
beginCourse: {
|
beginCourse: {
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
import { MailtoLink, Hyperlink } from '@openedx/paragon';
|
import { MailtoLink, Hyperlink } from '@openedx/paragon';
|
||||||
import { CheckCircle } from '@openedx/paragon/icons';
|
import { CheckCircle } from '@openedx/paragon/icons';
|
||||||
import { useIntl } from '@openedx/frontend-base';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { utilHooks, reduxHooks } from '../../../../hooks';
|
import { utilHooks, reduxHooks } from 'hooks';
|
||||||
import Banner from '../../../../components/Banner';
|
import Banner from 'components/Banner';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ export const CertificateBanner = ({ cardId }) => {
|
|||||||
if (certificate.isRestricted) {
|
if (certificate.isRestricted) {
|
||||||
return (
|
return (
|
||||||
<Banner variant="danger">
|
<Banner variant="danger">
|
||||||
{supportEmail ? formatMessage(messages.certRestricted, { supportEmail: emailLink(supportEmail) }) : formatMessage(messages.certRestrictedNoEmail)}
|
{ supportEmail ? formatMessage(messages.certRestricted, { supportEmail: emailLink(supportEmail) }) : formatMessage(messages.certRestrictedNoEmail)}
|
||||||
{isVerified && ' '}
|
{isVerified && ' '}
|
||||||
{isVerified && (billingEmail ? formatMessage(messages.certRefundContactBilling, { billingEmail: emailLink(billingEmail) }) : formatMessage(messages.certRefundContactBillingNoEmail))}
|
{isVerified && (billingEmail ? formatMessage(messages.certRefundContactBilling, { billingEmail: emailLink(billingEmail) }) : formatMessage(messages.certRefundContactBillingNoEmail))}
|
||||||
</Banner>
|
</Banner>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { IntlProvider } from '@openedx/frontend-base';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { reduxHooks } from '@src/hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import CertificateBanner from './CertificateBanner';
|
import CertificateBanner from './CertificateBanner';
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
utilHooks: {
|
utilHooks: {
|
||||||
useFormatDate: jest.fn(() => date => date),
|
useFormatDate: jest.fn(() => date => date),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,11 +2,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Hyperlink } from '@openedx/paragon';
|
import { Hyperlink } from '@openedx/paragon';
|
||||||
import { useIntl } from '@openedx/frontend-base';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { utilHooks, reduxHooks } from '../../../../hooks';
|
|
||||||
import Banner from '../../../../components/Banner';
|
|
||||||
|
|
||||||
|
import { utilHooks, reduxHooks } from 'hooks';
|
||||||
|
import Banner from 'components/Banner';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
export const CourseBanner = ({ cardId }) => {
|
export const CourseBanner = ({ cardId }) => {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { IntlProvider } from '@openedx/frontend-base';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { reduxHooks } from '@src/hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import { formatMessage } from '@src/testUtils';
|
import { formatMessage } from 'testUtils';
|
||||||
import { CourseBanner } from './CourseBanner';
|
import { CourseBanner } from './CourseBanner';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
utilHooks: {
|
utilHooks: {
|
||||||
useFormatDate: () => date => date,
|
useFormatDate: () => date => date,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { StrictDict } from '../../../../../utils';
|
import { StrictDict } from 'utils';
|
||||||
import { reduxHooks } from '../../../../../hooks';
|
|
||||||
|
import { reduxHooks } from 'hooks';
|
||||||
|
|
||||||
import ApprovedContent from './views/ApprovedContent';
|
import ApprovedContent from './views/ApprovedContent';
|
||||||
import EligibleContent from './views/EligibleContent';
|
import EligibleContent from './views/EligibleContent';
|
||||||
@@ -16,9 +17,7 @@ export const statusComponents = StrictDict({
|
|||||||
export const useCreditBannerData = (cardId) => {
|
export const useCreditBannerData = (cardId) => {
|
||||||
const credit = reduxHooks.useCardCreditData(cardId);
|
const credit = reduxHooks.useCardCreditData(cardId);
|
||||||
const { supportEmail } = reduxHooks.usePlatformSettingsData();
|
const { supportEmail } = reduxHooks.usePlatformSettingsData();
|
||||||
if (!credit.isEligible) {
|
if (!credit.isEligible) { return null; }
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { error, purchased, requestStatus } = credit;
|
const { error, purchased, requestStatus } = credit;
|
||||||
let ContentComponent = EligibleContent;
|
let ContentComponent = EligibleContent;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { keyStore } from '@src/utils';
|
import { keyStore } from 'utils';
|
||||||
import { reduxHooks } from '@src/hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
|
|
||||||
import ApprovedContent from './views/ApprovedContent';
|
import ApprovedContent from './views/ApprovedContent';
|
||||||
import EligibleContent from './views/EligibleContent';
|
import EligibleContent from './views/EligibleContent';
|
||||||
@@ -9,7 +9,7 @@ import RejectedContent from './views/RejectedContent';
|
|||||||
|
|
||||||
import * as hooks from './hooks';
|
import * as hooks from './hooks';
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
useCardCreditData: jest.fn(),
|
useCardCreditData: jest.fn(),
|
||||||
usePlatformSettingsData: jest.fn(),
|
usePlatformSettingsData: jest.fn(),
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@openedx/frontend-base';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import Banner from '../../../../../components/Banner';
|
import Banner from 'components/Banner';
|
||||||
|
|
||||||
import { MailtoLink } from '@openedx/paragon';
|
import { MailtoLink } from '@openedx/paragon';
|
||||||
import hooks from './hooks';
|
import hooks from './hooks';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { screen, render } from '@testing-library/react';
|
import { screen, render } from '@testing-library/react';
|
||||||
import { IntlProvider } from '@openedx/frontend-base';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
import hooks from './hooks';
|
import hooks from './hooks';
|
||||||
import { CreditBanner } from '.';
|
import { CreditBanner } from '.';
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { defineMessages } from '@openedx/frontend-base';
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
error: {
|
error: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.error',
|
id: 'learner-dash.courseCard.banners.credit.error',
|
||||||
description: 'Error message for credit transaction with support email link',
|
description: '',
|
||||||
defaultMessage: 'An error occurred with this transaction. For help, contact {supportEmailLink}.',
|
defaultMessage: 'An error occurred with this transaction. For help, contact {supportEmailLink}.',
|
||||||
},
|
},
|
||||||
errorNoEmail: {
|
errorNoEmail: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.errorNoEmail',
|
id: 'learner-dash.courseCard.banners.credit.errorNoEmail',
|
||||||
description: 'Error message for credit transaction without support email',
|
description: '',
|
||||||
defaultMessage: 'An error occurred with this transaction.',
|
defaultMessage: 'An error occurred with this transaction.',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useContext } from 'react';
|
|
||||||
import { useIntl } from '@openedx/frontend-base';
|
|
||||||
|
|
||||||
import MasqueradeUserContext from '../../../../../../data/contexts/MasqueradeUserContext';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { reduxHooks } from '../../../../../../hooks';
|
|
||||||
|
import { reduxHooks } from 'hooks';
|
||||||
import CreditContent from './components/CreditContent';
|
import CreditContent from './components/CreditContent';
|
||||||
import ProviderLink from './components/ProviderLink';
|
import ProviderLink from './components/ProviderLink';
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ import messages from './messages';
|
|||||||
|
|
||||||
export const ApprovedContent = ({ cardId }) => {
|
export const ApprovedContent = ({ cardId }) => {
|
||||||
const { providerStatusUrl: href, providerName } = reduxHooks.useCardCreditData(cardId);
|
const { providerStatusUrl: href, providerName } = reduxHooks.useCardCreditData(cardId);
|
||||||
const { isMasquerading } = useContext(MasqueradeUserContext);
|
const { isMasquerading } = reduxHooks.useMasqueradeData();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
return (
|
return (
|
||||||
<CreditContent
|
<CreditContent
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { IntlProvider } from '@openedx/frontend-base';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
import { formatMessage } from '@src/testUtils';
|
import { formatMessage } from 'testUtils';
|
||||||
import { reduxHooks } from '@src/hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import MasqueradeUserContext from '@src/data/contexts/MasqueradeUserContext';
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
import ApprovedContent from './ApprovedContent';
|
import ApprovedContent from './ApprovedContent';
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
useCardCreditData: jest.fn(),
|
useCardCreditData: jest.fn(),
|
||||||
|
useMasqueradeData: jest.fn(),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -18,19 +18,12 @@ const credit = {
|
|||||||
providerName: 'test-credit-provider-name',
|
providerName: 'test-credit-provider-name',
|
||||||
};
|
};
|
||||||
reduxHooks.useCardCreditData.mockReturnValue(credit);
|
reduxHooks.useCardCreditData.mockReturnValue(credit);
|
||||||
|
reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: false });
|
||||||
const renderWithMasquerading = (isMasquerading = false) => render(
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<MasqueradeUserContext.Provider value={{ isMasquerading }}>
|
|
||||||
<ApprovedContent cardId={cardId} />
|
|
||||||
</MasqueradeUserContext.Provider>
|
|
||||||
</IntlProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('ApprovedContent component', () => {
|
describe('ApprovedContent component', () => {
|
||||||
describe('hooks', () => {
|
describe('hooks', () => {
|
||||||
it('initializes credit data with cardId', () => {
|
it('initializes credit data with cardId', () => {
|
||||||
renderWithMasquerading();
|
render(<IntlProvider locale="en"><ApprovedContent cardId={cardId} /></IntlProvider>);
|
||||||
expect(reduxHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
expect(reduxHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -38,7 +31,7 @@ describe('ApprovedContent component', () => {
|
|||||||
describe('rendered CreditContent component', () => {
|
describe('rendered CreditContent component', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
renderWithMasquerading();
|
render(<IntlProvider locale="en"><ApprovedContent cardId={cardId} /></IntlProvider>);
|
||||||
});
|
});
|
||||||
it('action.message is formatted viewCredit message', () => {
|
it('action.message is formatted viewCredit message', () => {
|
||||||
const actionButton = screen.getByRole('link', { name: messages.viewCredit.defaultMessage });
|
const actionButton = screen.getByRole('link', { name: messages.viewCredit.defaultMessage });
|
||||||
@@ -63,7 +56,8 @@ describe('ApprovedContent component', () => {
|
|||||||
});
|
});
|
||||||
describe('when masquerading', () => {
|
describe('when masquerading', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
renderWithMasquerading(true);
|
reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: true });
|
||||||
|
render(<IntlProvider locale="en"><ApprovedContent cardId={cardId} /></IntlProvider>);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('disables the action button', () => {
|
it('disables the action button', () => {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@openedx/frontend-base';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { reduxHooks } from '../../../../../../hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import track from '../../../../../../tracking';
|
import track from 'tracking';
|
||||||
|
|
||||||
import CreditContent from './components/CreditContent';
|
import CreditContent from './components/CreditContent';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { IntlProvider } from '@openedx/frontend-base';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { reduxHooks } from '@src/hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import track from '@src/tracking';
|
import track from 'tracking';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
import EligibleContent from './EligibleContent';
|
import EligibleContent from './EligibleContent';
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
useCardCreditData: jest.fn(),
|
useCardCreditData: jest.fn(),
|
||||||
useCardCourseRunData: jest.fn(),
|
useCardCourseRunData: jest.fn(),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('@src/tracking', () => ({
|
jest.mock('tracking', () => ({
|
||||||
credit: {
|
credit: {
|
||||||
purchase: jest.fn(),
|
purchase: jest.fn(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useContext } from 'react';
|
|
||||||
import { useIntl } from '@openedx/frontend-base';
|
|
||||||
|
|
||||||
import MasqueradeUserContext from '../../../../../../data/contexts/MasqueradeUserContext';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
import { reduxHooks } from 'hooks';
|
||||||
import CreditContent from './components/CreditContent';
|
import CreditContent from './components/CreditContent';
|
||||||
import ProviderLink from './components/ProviderLink';
|
import ProviderLink from './components/ProviderLink';
|
||||||
import hooks from './hooks';
|
import hooks from './hooks';
|
||||||
@@ -12,7 +13,7 @@ import messages from './messages';
|
|||||||
export const MustRequestContent = ({ cardId }) => {
|
export const MustRequestContent = ({ cardId }) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const { requestData, createCreditRequest } = hooks.useCreditRequestData(cardId);
|
const { requestData, createCreditRequest } = hooks.useCreditRequestData(cardId);
|
||||||
const { isMasquerading } = useContext(MasqueradeUserContext);
|
const { isMasquerading } = reduxHooks.useMasqueradeData();
|
||||||
return (
|
return (
|
||||||
<CreditContent
|
<CreditContent
|
||||||
action={{
|
action={{
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { IntlProvider } from '@openedx/frontend-base';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
import { reduxHooks } from '@src/hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import MasqueradeUserContext from '@src/data/contexts/MasqueradeUserContext';
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
import hooks from './hooks';
|
import hooks from './hooks';
|
||||||
import MustRequestContent from './MustRequestContent';
|
import MustRequestContent from './MustRequestContent';
|
||||||
@@ -12,8 +11,9 @@ jest.mock('./hooks', () => ({
|
|||||||
useCreditRequestData: jest.fn(),
|
useCreditRequestData: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
|
useMasqueradeData: jest.fn(),
|
||||||
useCardCreditData: jest.fn(),
|
useCardCreditData: jest.fn(),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@@ -31,11 +31,9 @@ const providerName = 'test-credit-provider-name';
|
|||||||
const providerStatusUrl = 'test-credit-provider-status-url';
|
const providerStatusUrl = 'test-credit-provider-status-url';
|
||||||
const createCreditRequest = jest.fn().mockName('createCreditRequest');
|
const createCreditRequest = jest.fn().mockName('createCreditRequest');
|
||||||
|
|
||||||
const renderMustRequestContent = (isMasquerading = false) => render(
|
const renderMustRequestContent = () => render(
|
||||||
<IntlProvider locale="en" messages={messages}>
|
<IntlProvider locale="en" messages={messages}>
|
||||||
<MasqueradeUserContext.Provider value={{ isMasquerading }}>
|
<MustRequestContent cardId={cardId} />
|
||||||
<MustRequestContent cardId={cardId} />
|
|
||||||
</MasqueradeUserContext.Provider>
|
|
||||||
</IntlProvider>,
|
</IntlProvider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -46,6 +44,7 @@ describe('MustRequestContent component', () => {
|
|||||||
requestData,
|
requestData,
|
||||||
createCreditRequest,
|
createCreditRequest,
|
||||||
});
|
});
|
||||||
|
reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: false });
|
||||||
reduxHooks.useCardCreditData.mockReturnValue({
|
reduxHooks.useCardCreditData.mockReturnValue({
|
||||||
providerName,
|
providerName,
|
||||||
providerStatusUrl,
|
providerStatusUrl,
|
||||||
@@ -91,13 +90,14 @@ describe('MustRequestContent component', () => {
|
|||||||
|
|
||||||
describe('when masquerading', () => {
|
describe('when masquerading', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
renderMustRequestContent(true);
|
reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: true });
|
||||||
|
renderMustRequestContent();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('disables the request credit button', () => {
|
it('disables the request credit button', () => {
|
||||||
const button = screen.getByRole('button', { name: /request credit/i });
|
const button = screen.getByRole('button', { name: /request credit/i });
|
||||||
expect(button).toHaveAttribute('aria-disabled', 'true');
|
|
||||||
expect(button).toHaveClass('disabled');
|
expect(button).toHaveClass('disabled');
|
||||||
|
expect(button).toHaveAttribute('aria-disabled', 'true');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useContext } from 'react';
|
|
||||||
import { useIntl } from '@openedx/frontend-base';
|
|
||||||
|
|
||||||
import MasqueradeUserContext from '../../../../../../data/contexts/MasqueradeUserContext';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { reduxHooks } from '../../../../../../hooks';
|
|
||||||
|
import { reduxHooks } from 'hooks';
|
||||||
import CreditContent from './components/CreditContent';
|
import CreditContent from './components/CreditContent';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
export const PendingContent = ({ cardId }) => {
|
export const PendingContent = ({ cardId }) => {
|
||||||
const { providerStatusUrl: href, providerName } = reduxHooks.useCardCreditData(cardId);
|
const { providerStatusUrl: href, providerName } = reduxHooks.useCardCreditData(cardId);
|
||||||
const { isMasquerading } = useContext(MasqueradeUserContext);
|
const { isMasquerading } = reduxHooks.useMasqueradeData();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
return (
|
return (
|
||||||
<CreditContent
|
<CreditContent
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { IntlProvider } from '@openedx/frontend-base';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { reduxHooks } from '@src/hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import MasqueradeUserContext from '@src/data/contexts/MasqueradeUserContext';
|
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
import PendingContent from './PendingContent';
|
import PendingContent from './PendingContent';
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: { useCardCreditData: jest.fn() },
|
reduxHooks: { useCardCreditData: jest.fn(), useMasqueradeData: jest.fn() },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const cardId = 'test-card-id';
|
const cardId = 'test-card-id';
|
||||||
@@ -18,12 +17,11 @@ reduxHooks.useCardCreditData.mockReturnValue({
|
|||||||
providerName,
|
providerName,
|
||||||
providerStatusUrl,
|
providerStatusUrl,
|
||||||
});
|
});
|
||||||
|
reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: false });
|
||||||
|
|
||||||
const renderPendingContent = (isMasquerading = false) => render(
|
const renderPendingContent = () => render(
|
||||||
<IntlProvider messages={{}} locale="en">
|
<IntlProvider messages={{}} locale="en">
|
||||||
<MasqueradeUserContext.Provider value={{ isMasquerading }}>
|
<PendingContent cardId={cardId} />
|
||||||
<PendingContent cardId={cardId} />
|
|
||||||
</MasqueradeUserContext.Provider>
|
|
||||||
</IntlProvider>,
|
</IntlProvider>,
|
||||||
);
|
);
|
||||||
describe('PendingContent component', () => {
|
describe('PendingContent component', () => {
|
||||||
@@ -58,9 +56,9 @@ describe('PendingContent component', () => {
|
|||||||
});
|
});
|
||||||
describe('when masqueradeData is true', () => {
|
describe('when masqueradeData is true', () => {
|
||||||
it('disables the view details button', () => {
|
it('disables the view details button', () => {
|
||||||
renderPendingContent(true);
|
reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: true });
|
||||||
|
renderPendingContent();
|
||||||
const button = screen.getByRole('link', { name: messages.viewDetails.defaultMessage });
|
const button = screen.getByRole('link', { name: messages.viewDetails.defaultMessage });
|
||||||
expect(button).toHaveAttribute('aria-disabled', 'true');
|
|
||||||
expect(button).toHaveClass('disabled');
|
expect(button).toHaveClass('disabled');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@openedx/frontend-base';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { reduxHooks } from '../../../../../../hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import CreditContent from './components/CreditContent';
|
import CreditContent from './components/CreditContent';
|
||||||
import ProviderLink from './components/ProviderLink';
|
import ProviderLink from './components/ProviderLink';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { IntlProvider } from '@openedx/frontend-base';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { reduxHooks } from '@src/hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import RejectedContent from './RejectedContent';
|
import RejectedContent from './RejectedContent';
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
useCardCreditData: jest.fn(),
|
useCardCreditData: jest.fn(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
|
|
||||||
import { keyStore } from '@src/utils';
|
import { keyStore } from 'utils';
|
||||||
|
|
||||||
import useCreditRequestFormData from './hooks';
|
import useCreditRequestFormData from './hooks';
|
||||||
import CreditRequestForm from '.';
|
import CreditRequestForm from '.';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { reduxHooks } from '../../../../../../../hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import { Hyperlink } from '@openedx/paragon';
|
import { Hyperlink } from '@openedx/paragon';
|
||||||
|
|
||||||
export const ProviderLink = ({ cardId }) => {
|
export const ProviderLink = ({ cardId }) => {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { reduxHooks } from '@src/hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import { IntlProvider } from '@openedx/frontend-base';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import ProviderLink from './ProviderLink';
|
import ProviderLink from './ProviderLink';
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
useCardCreditData: jest.fn(),
|
useCardCreditData: jest.fn(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { StrictDict } from '../../../../../../utils';
|
import { StrictDict } from 'utils';
|
||||||
import { apiHooks } from '../../../../../../hooks';
|
import { apiHooks } from 'hooks';
|
||||||
|
|
||||||
import * as module from './hooks';
|
import * as module from './hooks';
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { MockUseState } from '@src/testUtils';
|
import { MockUseState } from 'testUtils';
|
||||||
import { apiHooks } from '@src/hooks';
|
import { apiHooks } from 'hooks';
|
||||||
import * as hooks from './hooks';
|
import * as hooks from './hooks';
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
apiHooks: {
|
apiHooks: {
|
||||||
useCreateCreditRequest: jest.fn(),
|
useCreateCreditRequest: jest.fn(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,59 +1,59 @@
|
|||||||
import { defineMessages } from '@openedx/frontend-base';
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
approved: {
|
approved: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.approved',
|
id: 'learner-dash.courseCard.banners.credit.approved',
|
||||||
description: 'Message shown when credit request has been approved',
|
description: '',
|
||||||
defaultMessage: '{congratulations} {providerName} has approved your request for course credit. To see your course credit, visit the {linkToProviderSite} website.',
|
defaultMessage: '{congratulations} {providerName} has approved your request for course credit. To see your course credit, visit the {linkToProviderSite} website.',
|
||||||
},
|
},
|
||||||
congratulations: {
|
congratulations: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.congratulations',
|
id: 'learner-dash.courseCard.banners.credit.congratulations',
|
||||||
description: 'Congratulatory message for credit approval',
|
description: '',
|
||||||
defaultMessage: 'Congratulations!',
|
defaultMessage: 'Congratulations!',
|
||||||
},
|
},
|
||||||
eligible: {
|
eligible: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.eligible',
|
id: 'learner-dash.courseCard.banners.credit.eligible',
|
||||||
description: 'Message shown when user is eligible to purchase course credit',
|
description: '',
|
||||||
defaultMessage: 'You have completed this course and are eligible to purchase course credit. Select {getCredit} to get started.',
|
defaultMessage: 'You have completed this course and are eligible to purchase course credit. Select {getCredit} to get started.',
|
||||||
},
|
},
|
||||||
eligibleFromProvider: {
|
eligibleFromProvider: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.eligibleFromProvider',
|
id: 'learner-dash.courseCard.banners.credit.eligibleFromProvider',
|
||||||
description: 'Message shown when user is eligible for credit from a specific provider',
|
description: '',
|
||||||
defaultMessage: 'You are now eligible for credit from {providerName}. Congratulations!',
|
defaultMessage: 'You are now eligible for credit from {providerName}. Congratulations!',
|
||||||
},
|
},
|
||||||
getCredit: {
|
getCredit: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.getCredit',
|
id: 'learner-dash.courseCard.banners.credit.getCredit',
|
||||||
description: 'Button text for initiating the credit process',
|
description: '',
|
||||||
defaultMessage: 'Get Credit',
|
defaultMessage: 'Get Credit',
|
||||||
},
|
},
|
||||||
mustRequest: {
|
mustRequest: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.mustRequest',
|
id: 'learner-dash.courseCard.banners.credit.mustRequest',
|
||||||
description: 'Message shown after payment to instruct user to request credit',
|
description: '',
|
||||||
defaultMessage: 'Thank you for your payment. To receive course credit, you must request credit at the {linkToProviderSite} website. Select {requestCredit} to get started',
|
defaultMessage: 'Thank you for your payment. To receive course credit, you must request credit at the {linkToProviderSite} website. Select {requestCredit} to get started',
|
||||||
},
|
},
|
||||||
received: {
|
received: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.received',
|
id: 'learner-dash.courseCard.banners.credit.received',
|
||||||
description: 'Message shown when credit request has been received',
|
description: '',
|
||||||
defaultMessage: '{providerName} has received your course credit request. We will update you when credit processing is complete.',
|
defaultMessage: '{providerName} has received your course credit request. We will update you when credit processing is complete.',
|
||||||
},
|
},
|
||||||
rejected: {
|
rejected: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.rejected',
|
id: 'learner-dash.courseCard.banners.credit.rejected',
|
||||||
description: 'Message shown when credit request has been rejected',
|
description: '',
|
||||||
defaultMessage: '{providerName} did not approve your request for course credit. For more information, contact {linkToProviderSite} directly.',
|
defaultMessage: '{providerName} did not approve your request for course credit. For more information, contact {linkToProviderSite} directly.',
|
||||||
},
|
},
|
||||||
requestCredit: {
|
requestCredit: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.requestCredit',
|
id: 'learner-dash.courseCard.banners.credit.requestCredit',
|
||||||
description: 'Button text for requesting credit',
|
description: '',
|
||||||
defaultMessage: 'Request Credit',
|
defaultMessage: 'Request Credit',
|
||||||
},
|
},
|
||||||
viewCredit: {
|
viewCredit: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.viewCredit',
|
id: 'learner-dash.courseCard.banners.credit.viewCredit',
|
||||||
description: 'Button text for viewing credit details',
|
description: '',
|
||||||
defaultMessage: 'View Credit',
|
defaultMessage: 'View Credit',
|
||||||
},
|
},
|
||||||
viewDetails: {
|
viewDetails: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.viewDetails',
|
id: 'learner-dash.courseCard.banners.credit.viewDetails',
|
||||||
description: 'Button text for viewing credit request details',
|
description: '',
|
||||||
defaultMessage: 'View Details',
|
defaultMessage: 'View Details',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@openedx/frontend-base';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { Button, MailtoLink } from '@openedx/paragon';
|
import { Button, MailtoLink } from '@openedx/paragon';
|
||||||
|
|
||||||
import { utilHooks, reduxHooks } from '../../../../hooks';
|
import { utilHooks, reduxHooks } from 'hooks';
|
||||||
import Banner from '../../../../components/Banner';
|
|
||||||
|
|
||||||
|
import Banner from 'components/Banner';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
export const EntitlementBanner = ({ cardId }) => {
|
export const EntitlementBanner = ({ cardId }) => {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { IntlProvider } from '@openedx/frontend-base';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
import { formatMessage } from '@src/testUtils';
|
import { formatMessage } from 'testUtils';
|
||||||
|
|
||||||
import { reduxHooks } from '@src/hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import EntitlementBanner from './EntitlementBanner';
|
import EntitlementBanner from './EntitlementBanner';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
utilHooks: {
|
utilHooks: {
|
||||||
useFormatDate: () => date => date,
|
useFormatDate: () => date => date,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { Program } from '@openedx/paragon/icons';
|
import { Program } from '@openedx/paragon/icons';
|
||||||
import { useIntl } from '@openedx/frontend-base';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { reduxHooks } from '../../../../../hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import Banner from '../../../../../components/Banner';
|
import Banner from 'components/Banner';
|
||||||
|
|
||||||
import ProgramList from './ProgramsList';
|
import ProgramList from './ProgramsList';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { IntlProvider } from '@openedx/frontend-base';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { reduxHooks } from '@src/hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import RelatedProgramsBanner from '.';
|
import RelatedProgramsBanner from '.';
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
useCardRelatedProgramsData: jest.fn(),
|
useCardRelatedProgramsData: jest.fn(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineMessages } from '@openedx/frontend-base';
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
relatedPrograms: {
|
relatedPrograms: {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { reduxHooks } from '../../../../hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import CourseBannerSlot from '../../../../slots/CourseBannerSlot';
|
|
||||||
|
|
||||||
|
import CourseBannerSlot from 'plugin-slots/CourseBannerSlot';
|
||||||
import CertificateBanner from './CertificateBanner';
|
import CertificateBanner from './CertificateBanner';
|
||||||
import CreditBanner from './CreditBanner';
|
import CreditBanner from './CreditBanner';
|
||||||
import EntitlementBanner from './EntitlementBanner';
|
import EntitlementBanner from './EntitlementBanner';
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { IntlProvider } from '@openedx/frontend-base';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
|
||||||
|
import { reduxHooks } from 'hooks';
|
||||||
|
|
||||||
import { reduxHooks } from '@src/hooks';
|
|
||||||
import CourseCardBanners from '.';
|
import CourseCardBanners from '.';
|
||||||
|
|
||||||
jest.mock('./CourseBanner', () => jest.fn(() => <div>CourseBanner</div>));
|
jest.mock('./CourseBanner', () => jest.fn(() => <div>CourseBanner</div>));
|
||||||
@@ -19,7 +19,7 @@ const mockedComponents = [
|
|||||||
'RelatedProgramsBanner',
|
'RelatedProgramsBanner',
|
||||||
];
|
];
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
useCardEnrollmentData: jest.fn(() => ({ isEnrolled: true })),
|
useCardEnrollmentData: jest.fn(() => ({ isEnrolled: true })),
|
||||||
},
|
},
|
||||||
@@ -30,14 +30,7 @@ describe('CourseCardBanners', () => {
|
|||||||
cardId: 'test-card-id',
|
cardId: 'test-card-id',
|
||||||
};
|
};
|
||||||
it('renders default CourseCardBanners', () => {
|
it('renders default CourseCardBanners', () => {
|
||||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ isEnrolled: true });
|
render(<IntlProvider locale="en"><CourseCardBanners {...props} /></IntlProvider>);
|
||||||
render(
|
|
||||||
<MemoryRouter>
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<CourseCardBanners {...props} />
|
|
||||||
</IntlProvider>
|
|
||||||
</MemoryRouter>
|
|
||||||
);
|
|
||||||
mockedComponents.map((componentName) => {
|
mockedComponents.map((componentName) => {
|
||||||
const mockedComponent = screen.getByText(componentName);
|
const mockedComponent = screen.getByText(componentName);
|
||||||
return expect(mockedComponent).toBeInTheDocument();
|
return expect(mockedComponent).toBeInTheDocument();
|
||||||
@@ -45,13 +38,7 @@ describe('CourseCardBanners', () => {
|
|||||||
});
|
});
|
||||||
it('render with isEnrolled false', () => {
|
it('render with isEnrolled false', () => {
|
||||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ isEnrolled: false });
|
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ isEnrolled: false });
|
||||||
render(
|
render(<IntlProvider locale="en"><CourseCardBanners {...props} /></IntlProvider>);
|
||||||
<MemoryRouter>
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<CourseCardBanners {...props} />
|
|
||||||
</IntlProvider>
|
|
||||||
</MemoryRouter>
|
|
||||||
);
|
|
||||||
const mockedComponentsIfNotEnrolled = mockedComponents.slice(-2);
|
const mockedComponentsIfNotEnrolled = mockedComponents.slice(-2);
|
||||||
mockedComponentsIfNotEnrolled.map((componentName) => {
|
mockedComponentsIfNotEnrolled.map((componentName) => {
|
||||||
const mockedComponent = screen.getByText(componentName);
|
const mockedComponent = screen.getByText(componentName);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineMessages } from '@openedx/frontend-base';
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
auditAccessExpired: {
|
auditAccessExpired: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useIntl } from '@openedx/frontend-base';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { utilHooks, reduxHooks } from '../../../../hooks';
|
import { utilHooks, reduxHooks } from 'hooks';
|
||||||
|
|
||||||
import * as hooks from './hooks';
|
import * as hooks from './hooks';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
@@ -10,10 +10,8 @@ export const useAccessMessage = ({ cardId }) => {
|
|||||||
const courseRun = reduxHooks.useCardCourseRunData(cardId);
|
const courseRun = reduxHooks.useCardCourseRunData(cardId);
|
||||||
const formatDate = utilHooks.useFormatDate();
|
const formatDate = utilHooks.useFormatDate();
|
||||||
if (!courseRun.isStarted) {
|
if (!courseRun.isStarted) {
|
||||||
if (!courseRun.startDate && !courseRun.advertisedStart) {
|
if (!courseRun.startDate && !courseRun.advertisedStart) { return null; }
|
||||||
return null;
|
const startDate = courseRun.advertisedStart ? courseRun.advertisedStart : formatDate(courseRun.startDate);
|
||||||
}
|
|
||||||
const startDate = courseRun.advertisedStart ?? formatDate(courseRun.startDate);
|
|
||||||
return formatMessage(messages.courseStarts, { startDate });
|
return formatMessage(messages.courseStarts, { startDate });
|
||||||
}
|
}
|
||||||
if (enrollment.isEnrolled) {
|
if (enrollment.isEnrolled) {
|
||||||
@@ -29,9 +27,7 @@ export const useAccessMessage = ({ cardId }) => {
|
|||||||
{ accessExpirationDate: formatDate(accessExpirationDate) },
|
{ accessExpirationDate: formatDate(accessExpirationDate) },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!endDate) {
|
if (!endDate) { return null; }
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return formatMessage(
|
return formatMessage(
|
||||||
isArchived ? messages.courseEnded : messages.courseEnds,
|
isArchived ? messages.courseEnded : messages.courseEnds,
|
||||||
{ endDate: formatDate(endDate) },
|
{ endDate: formatDate(endDate) },
|
||||||
@@ -53,7 +49,7 @@ export const useCardDetailsData = ({ cardId }) => {
|
|||||||
const openSessionModal = reduxHooks.useUpdateSelectSessionModalCallback(cardId);
|
const openSessionModal = reduxHooks.useUpdateSelectSessionModalCallback(cardId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
providerName: providerName ?? formatMessage(messages.unknownProviderName),
|
providerName: providerName || formatMessage(messages.unknownProviderName),
|
||||||
accessMessage: hooks.useAccessMessage({ cardId }),
|
accessMessage: hooks.useAccessMessage({ cardId }),
|
||||||
isEntitlement,
|
isEntitlement,
|
||||||
isFulfilled,
|
isFulfilled,
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { useIntl } from '@openedx/frontend-base';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
import { keyStore } from 'utils';
|
||||||
|
import { utilHooks, reduxHooks } from 'hooks';
|
||||||
|
|
||||||
import { keyStore } from '@src/utils';
|
|
||||||
import { utilHooks, reduxHooks } from '@src/hooks';
|
|
||||||
import * as hooks from './hooks';
|
import * as hooks from './hooks';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
utilHooks: {
|
utilHooks: {
|
||||||
useFormatDate: jest.fn(),
|
useFormatDate: jest.fn(),
|
||||||
},
|
},
|
||||||
@@ -19,10 +20,10 @@ jest.mock('@src/hooks', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('@openedx/frontend-base', () => {
|
jest.mock('@edx/frontend-platform/i18n', () => {
|
||||||
const { formatMessage } = jest.requireActual('@src/testUtils');
|
const { formatMessage } = jest.requireActual('testUtils');
|
||||||
return {
|
return {
|
||||||
...jest.requireActual('@openedx/frontend-base'),
|
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||||
useIntl: () => ({
|
useIntl: () => ({
|
||||||
formatMessage,
|
formatMessage,
|
||||||
}),
|
}),
|
||||||
@@ -45,8 +46,9 @@ describe('CourseCardDetails hooks', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('useCardDetailsData', () => {
|
describe('useCardDetailsData', () => {
|
||||||
const providerName = 'my-provider-name';
|
const providerData = {
|
||||||
const providerData = {};
|
name: 'my-provider-name',
|
||||||
|
};
|
||||||
const entitlementData = {
|
const entitlementData = {
|
||||||
isEntitlement: false,
|
isEntitlement: false,
|
||||||
disableViewCourse: false,
|
disableViewCourse: false,
|
||||||
@@ -76,10 +78,8 @@ describe('CourseCardDetails hooks', () => {
|
|||||||
expect(out.accessMessage).toEqual(mockAccessMessage({ cardId }));
|
expect(out.accessMessage).toEqual(mockAccessMessage({ cardId }));
|
||||||
});
|
});
|
||||||
it('forwards provider name if it exists, else formatted unknown provider name', () => {
|
it('forwards provider name if it exists, else formatted unknown provider name', () => {
|
||||||
runHook({ provider: { name: providerName } });
|
expect(out.providerName).toEqual(providerData.name);
|
||||||
expect(out.providerName).toEqual(providerName);
|
runHook({ provider: { name: '' } });
|
||||||
|
|
||||||
runHook({ provider: {} });
|
|
||||||
expect(out.providerName).toEqual(formatMessage(messages.unknownProviderName));
|
expect(out.providerName).toEqual(formatMessage(messages.unknownProviderName));
|
||||||
});
|
});
|
||||||
it('forward changeOrLeaveSessionMessage', () => {
|
it('forward changeOrLeaveSessionMessage', () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineMessages } from '@openedx/frontend-base';
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
accessExpired: {
|
accessExpired: {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useIntl } from '@openedx/frontend-base';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { Badge } from '@openedx/paragon';
|
import { Badge } from '@openedx/paragon';
|
||||||
|
|
||||||
import track from '../../../tracking';
|
import track from 'tracking';
|
||||||
import { reduxHooks } from '../../../hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import verifiedRibbon from '../../../assets/verified-ribbon.png';
|
import verifiedRibbon from 'assets/verified-ribbon.png';
|
||||||
import useActionDisabledState from './hooks';
|
import useActionDisabledState from './hooks';
|
||||||
|
|
||||||
import messages from '../messages';
|
import messages from '../messages';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { IntlProvider } from '@openedx/frontend-base';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
import { formatMessage } from '@src/testUtils';
|
import { formatMessage } from 'testUtils';
|
||||||
import { reduxHooks } from '@src/hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import useActionDisabledState from './hooks';
|
import useActionDisabledState from './hooks';
|
||||||
import { CourseCardImage } from './CourseCardImage';
|
import { CourseCardImage } from './CourseCardImage';
|
||||||
import messages from '../messages';
|
import messages from '../messages';
|
||||||
@@ -9,7 +9,7 @@ import messages from '../messages';
|
|||||||
const homeUrl = 'https://example.com';
|
const homeUrl = 'https://example.com';
|
||||||
const bannerImgSrc = 'banner-img-src.jpg';
|
const bannerImgSrc = 'banner-img-src.jpg';
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
useCardCourseData: jest.fn(() => ({ bannerImgSrc })),
|
useCardCourseData: jest.fn(() => ({ bannerImgSrc })),
|
||||||
useCardCourseRunData: jest.fn(() => ({ homeUrl })),
|
useCardCourseRunData: jest.fn(() => ({ homeUrl })),
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { useContext } from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as ReactShare from 'react-share';
|
import * as ReactShare from 'react-share';
|
||||||
|
|
||||||
import { useIntl } from '@openedx/frontend-base';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { Dropdown } from '@openedx/paragon';
|
import { Dropdown } from '@openedx/paragon';
|
||||||
|
|
||||||
import MasqueradeUserContext from '../../../../data/contexts/MasqueradeUserContext';
|
import track from 'tracking';
|
||||||
import track from '../../../../tracking';
|
import { reduxHooks } from 'hooks';
|
||||||
import { reduxHooks } from '../../../../hooks';
|
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
@@ -21,7 +20,7 @@ export const SocialShareMenu = ({ cardId, emailSettings }) => {
|
|||||||
const { courseName } = reduxHooks.useCardCourseData(cardId);
|
const { courseName } = reduxHooks.useCardCourseData(cardId);
|
||||||
const { isEmailEnabled, isExecEd2UCourse } = reduxHooks.useCardEnrollmentData(cardId);
|
const { isEmailEnabled, isExecEd2UCourse } = reduxHooks.useCardEnrollmentData(cardId);
|
||||||
const { twitter, facebook } = reduxHooks.useCardSocialSettingsData(cardId);
|
const { twitter, facebook } = reduxHooks.useCardSocialSettingsData(cardId);
|
||||||
const { isMasquerading } = useContext(MasqueradeUserContext);
|
const { isMasquerading } = reduxHooks.useMasqueradeData();
|
||||||
|
|
||||||
const handleTwitterShare = reduxHooks.useTrackCourseEvent(track.socialShare, cardId, 'twitter');
|
const handleTwitterShare = reduxHooks.useTrackCourseEvent(track.socialShare, cardId, 'twitter');
|
||||||
const handleFacebookShare = reduxHooks.useTrackCourseEvent(track.socialShare, cardId, 'facebook');
|
const handleFacebookShare = reduxHooks.useTrackCourseEvent(track.socialShare, cardId, 'facebook');
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
import { IntlProvider } from '@openedx/frontend-base';
|
|
||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
import { when } from 'jest-when';
|
import { when } from 'jest-when';
|
||||||
import track from '@src/tracking';
|
|
||||||
import { reduxHooks } from '@src/hooks';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
import MasqueradeUserContext from '@src/data/contexts/MasqueradeUserContext';
|
import { render, screen } from '@testing-library/react';
|
||||||
|
|
||||||
|
import track from 'tracking';
|
||||||
|
import { reduxHooks } from 'hooks';
|
||||||
|
|
||||||
import { useEmailSettings } from './hooks';
|
import { useEmailSettings } from './hooks';
|
||||||
import SocialShareMenu from './SocialShareMenu';
|
import SocialShareMenu from './SocialShareMenu';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
jest.mock('@src/tracking', () => ({
|
jest.mock('tracking', () => ({
|
||||||
socialShare: 'test-social-share-key',
|
socialShare: 'test-social-share-key',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
|
useMasqueradeData: jest.fn(),
|
||||||
useCardCourseData: jest.fn(),
|
useCardCourseData: jest.fn(),
|
||||||
useCardEnrollmentData: jest.fn(),
|
useCardEnrollmentData: jest.fn(),
|
||||||
useCardSocialSettingsData: jest.fn(),
|
useCardSocialSettingsData: jest.fn(),
|
||||||
@@ -63,6 +65,7 @@ const mockHooks = (returnVals = {}) => {
|
|||||||
{ isCardHook: true },
|
{ isCardHook: true },
|
||||||
);
|
);
|
||||||
mockHook(reduxHooks.useCardCourseData, { courseName }, { isCardHook: true });
|
mockHook(reduxHooks.useCardCourseData, { courseName }, { isCardHook: true });
|
||||||
|
mockHook(reduxHooks.useMasqueradeData, { isMasquerading: !!returnVals.isMasquerading });
|
||||||
mockHook(
|
mockHook(
|
||||||
reduxHooks.useCardSocialSettingsData,
|
reduxHooks.useCardSocialSettingsData,
|
||||||
{
|
{
|
||||||
@@ -73,13 +76,7 @@ const mockHooks = (returnVals = {}) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderComponent = (isMasquerading = false) => render(
|
const renderComponent = () => render(<IntlProvider locale="en"><SocialShareMenu {...props} /></IntlProvider>);
|
||||||
<IntlProvider locale="en">
|
|
||||||
<MasqueradeUserContext.Provider value={{ isMasquerading }}>
|
|
||||||
<SocialShareMenu {...props} />
|
|
||||||
</MasqueradeUserContext.Provider>
|
|
||||||
</IntlProvider>,
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('SocialShareMenu', () => {
|
describe('SocialShareMenu', () => {
|
||||||
describe('behavior', () => {
|
describe('behavior', () => {
|
||||||
@@ -94,6 +91,7 @@ describe('SocialShareMenu', () => {
|
|||||||
when(reduxHooks.useCardEnrollmentData).expectCalledWith(props.cardId);
|
when(reduxHooks.useCardEnrollmentData).expectCalledWith(props.cardId);
|
||||||
when(reduxHooks.useCardCourseData).expectCalledWith(props.cardId);
|
when(reduxHooks.useCardCourseData).expectCalledWith(props.cardId);
|
||||||
when(reduxHooks.useCardSocialSettingsData).expectCalledWith(props.cardId);
|
when(reduxHooks.useCardSocialSettingsData).expectCalledWith(props.cardId);
|
||||||
|
when(reduxHooks.useMasqueradeData).expectCalledWith();
|
||||||
when(reduxHooks.useTrackCourseEvent).expectCalledWith(track.socialShare, props.cardId, 'twitter');
|
when(reduxHooks.useTrackCourseEvent).expectCalledWith(track.socialShare, props.cardId, 'twitter');
|
||||||
when(reduxHooks.useTrackCourseEvent).expectCalledWith(track.socialShare, props.cardId, 'facebook');
|
when(reduxHooks.useTrackCourseEvent).expectCalledWith(track.socialShare, props.cardId, 'facebook');
|
||||||
});
|
});
|
||||||
@@ -118,9 +116,7 @@ describe('SocialShareMenu', () => {
|
|||||||
if (isMasquerading) {
|
if (isMasquerading) {
|
||||||
it('is disabled', () => {
|
it('is disabled', () => {
|
||||||
const emailSettingsButton = screen.getByRole('button', { name: messages.emailSettings.defaultMessage });
|
const emailSettingsButton = screen.getByRole('button', { name: messages.emailSettings.defaultMessage });
|
||||||
expect(emailSettingsButton).toBeInTheDocument();
|
|
||||||
expect(emailSettingsButton).toHaveAttribute('aria-disabled', 'true');
|
expect(emailSettingsButton).toHaveAttribute('aria-disabled', 'true');
|
||||||
expect(emailSettingsButton).toHaveClass('disabled');
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
it('is enabled', () => {
|
it('is enabled', () => {
|
||||||
@@ -171,8 +167,8 @@ describe('SocialShareMenu', () => {
|
|||||||
});
|
});
|
||||||
describe('masquerading', () => {
|
describe('masquerading', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockHooks({ isEmailEnabled: true });
|
mockHooks({ isEmailEnabled: true, isMasquerading: true });
|
||||||
renderComponent(true);
|
renderComponent();
|
||||||
});
|
});
|
||||||
testEmailSettingsDropdown(true);
|
testEmailSettingsDropdown(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
|
import track from 'tracking';
|
||||||
|
import { reduxHooks } from 'hooks';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { StrictDict } from 'utils';
|
||||||
import { reduxHooks } from '../../../../hooks';
|
|
||||||
import track from '../../../../tracking';
|
|
||||||
import { StrictDict } from '../../../../utils';
|
|
||||||
|
|
||||||
export const state = StrictDict({
|
export const state = StrictDict({
|
||||||
isUnenrollConfirmVisible: (val) => useState(val), // eslint-disable-line
|
isUnenrollConfirmVisible: (val) => useState(val), // eslint-disable-line
|
||||||
@@ -33,9 +32,7 @@ export const useHandleToggleDropdown = (cardId) => {
|
|||||||
cardId,
|
cardId,
|
||||||
);
|
);
|
||||||
return (isOpen) => {
|
return (isOpen) => {
|
||||||
if (isOpen) {
|
if (isOpen) { trackCourseEvent(); }
|
||||||
trackCourseEvent();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { reduxHooks } from '@src/hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import track from '@src/tracking';
|
import track from 'tracking';
|
||||||
import { MockUseState } from '@src/testUtils';
|
import { MockUseState } from 'testUtils';
|
||||||
|
|
||||||
import * as hooks from './hooks';
|
import * as hooks from './hooks';
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
useCardCertificateData: jest.fn(),
|
useCardCertificateData: jest.fn(),
|
||||||
useCardEnrollmentData: jest.fn(),
|
useCardEnrollmentData: jest.fn(),
|
||||||
@@ -68,9 +68,7 @@ describe('CourseCardMenu hooks', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('useHandleToggleDropdown', () => {
|
describe('useHandleToggleDropdown', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => { out = hooks.useHandleToggleDropdown(cardId); });
|
||||||
out = hooks.useHandleToggleDropdown(cardId);
|
|
||||||
});
|
|
||||||
describe('behavior', () => {
|
describe('behavior', () => {
|
||||||
it('initializes course event tracker with event name and card ID', () => {
|
it('initializes course event tracker with event name and card ID', () => {
|
||||||
expect(reduxHooks.useTrackCourseEvent).toHaveBeenCalledWith(
|
expect(reduxHooks.useTrackCourseEvent).toHaveBeenCalledWith(
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import { useContext } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@openedx/frontend-base';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { Dropdown, Icon, IconButton } from '@openedx/paragon';
|
import { Dropdown, Icon, IconButton } from '@openedx/paragon';
|
||||||
import { MoreVert } from '@openedx/paragon/icons';
|
import { MoreVert } from '@openedx/paragon/icons';
|
||||||
|
|
||||||
import MasqueradeUserContext from '../../../../data/contexts/MasqueradeUserContext';
|
import EmailSettingsModal from 'containers/EmailSettingsModal';
|
||||||
import EmailSettingsModal from '../../../../containers/EmailSettingsModal';
|
import UnenrollConfirmModal from 'containers/UnenrollConfirmModal';
|
||||||
import UnenrollConfirmModal from '../../../../containers/UnenrollConfirmModal';
|
import { reduxHooks } from 'hooks';
|
||||||
import { reduxHooks } from '../../../../hooks';
|
|
||||||
|
|
||||||
import SocialShareMenu from './SocialShareMenu';
|
import SocialShareMenu from './SocialShareMenu';
|
||||||
import {
|
import {
|
||||||
useEmailSettings,
|
useEmailSettings,
|
||||||
@@ -31,7 +28,7 @@ export const CourseCardMenu = ({ cardId }) => {
|
|||||||
const unenrollModal = useUnenrollData();
|
const unenrollModal = useUnenrollData();
|
||||||
const handleToggleDropdown = useHandleToggleDropdown(cardId);
|
const handleToggleDropdown = useHandleToggleDropdown(cardId);
|
||||||
const { shouldShowUnenrollItem, shouldShowDropdown } = useOptionVisibility(cardId);
|
const { shouldShowUnenrollItem, shouldShowDropdown } = useOptionVisibility(cardId);
|
||||||
const { isMasquerading } = useContext(MasqueradeUserContext);
|
const { isMasquerading } = reduxHooks.useMasqueradeData();
|
||||||
const { isEmailEnabled } = reduxHooks.useCardEnrollmentData(cardId);
|
const { isEmailEnabled } = reduxHooks.useCardEnrollmentData(cardId);
|
||||||
|
|
||||||
if (!shouldShowDropdown) {
|
if (!shouldShowDropdown) {
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
import { when } from 'jest-when';
|
import { when } from 'jest-when';
|
||||||
|
|
||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { IntlProvider } from '@openedx/frontend-base';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
import { reduxHooks } from '@src/hooks';
|
|
||||||
import MasqueradeUserContext from '@src/data/contexts/MasqueradeUserContext';
|
import { reduxHooks } from 'hooks';
|
||||||
import * as hooks from './hooks';
|
import * as hooks from './hooks';
|
||||||
import CourseCardMenu from '.';
|
import CourseCardMenu from '.';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
jest.mock('@src/hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
|
useMasqueradeData: jest.fn(),
|
||||||
useCardEnrollmentData: jest.fn(),
|
useCardEnrollmentData: jest.fn(),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
jest.mock('./SocialShareMenu', () => jest.fn(() => <div>SocialShareMenu</div>));
|
jest.mock('./SocialShareMenu', () => jest.fn(() => <div>SocialShareMenu</div>));
|
||||||
jest.mock('@src/containers/EmailSettingsModal', () => jest.fn(() => <div>EmailSettingsModal</div>));
|
jest.mock('containers/EmailSettingsModal', () => jest.fn(() => <div>EmailSettingsModal</div>));
|
||||||
jest.mock('@src/containers/UnenrollConfirmModal', () => jest.fn(() => <div>UnenrollConfirmModal</div>));
|
jest.mock('containers/UnenrollConfirmModal', () => jest.fn(() => <div>UnenrollConfirmModal</div>));
|
||||||
jest.mock('./hooks', () => ({
|
jest.mock('./hooks', () => ({
|
||||||
useEmailSettings: jest.fn(),
|
useEmailSettings: jest.fn(),
|
||||||
useUnenrollData: jest.fn(),
|
useUnenrollData: jest.fn(),
|
||||||
@@ -67,6 +69,7 @@ const mockHooks = (returnVals = {}) => {
|
|||||||
},
|
},
|
||||||
{ isCardHook: true },
|
{ isCardHook: true },
|
||||||
);
|
);
|
||||||
|
mockHook(reduxHooks.useMasqueradeData, { isMasquerading: !!returnVals.isMasquerading });
|
||||||
mockHook(
|
mockHook(
|
||||||
reduxHooks.useCardEnrollmentData,
|
reduxHooks.useCardEnrollmentData,
|
||||||
{ isEmailEnabled: !!returnVals.isEmailEnabled },
|
{ isEmailEnabled: !!returnVals.isEmailEnabled },
|
||||||
@@ -74,13 +77,7 @@ const mockHooks = (returnVals = {}) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderComponent = (isMasquerading = false) => render(
|
const renderComponent = () => render(<IntlProvider locale="en"><CourseCardMenu {...props} /></IntlProvider>);
|
||||||
<IntlProvider locale="en">
|
|
||||||
<MasqueradeUserContext.Provider value={{ isMasquerading }}>
|
|
||||||
<CourseCardMenu {...props} />
|
|
||||||
</MasqueradeUserContext.Provider>
|
|
||||||
</IntlProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('CourseCardMenu', () => {
|
describe('CourseCardMenu', () => {
|
||||||
describe('hooks', () => {
|
describe('hooks', () => {
|
||||||
@@ -95,6 +92,7 @@ describe('CourseCardMenu', () => {
|
|||||||
when(hooks.useOptionVisibility).expectCalledWith(props.cardId);
|
when(hooks.useOptionVisibility).expectCalledWith(props.cardId);
|
||||||
});
|
});
|
||||||
it('initializes redux hook data ', () => {
|
it('initializes redux hook data ', () => {
|
||||||
|
when(reduxHooks.useMasqueradeData).expectCalledWith();
|
||||||
when(reduxHooks.useCardEnrollmentData).expectCalledWith(props.cardId);
|
when(reduxHooks.useCardEnrollmentData).expectCalledWith(props.cardId);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -155,13 +153,14 @@ describe('CourseCardMenu', () => {
|
|||||||
});
|
});
|
||||||
describe('masquerading', () => {
|
describe('masquerading', () => {
|
||||||
it('renders but unenroll is disabled', async () => {
|
it('renders but unenroll is disabled', async () => {
|
||||||
mockHooks({ ...hookProps });
|
mockHooks({ ...hookProps, isMasquerading: true });
|
||||||
renderComponent(true);
|
renderComponent();
|
||||||
|
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const dropdown = screen.getByRole('button', { name: messages.dropdownAlt.defaultMessage });
|
const dropdown = screen.getByRole('button', { name: messages.dropdownAlt.defaultMessage });
|
||||||
expect(dropdown).toBeInTheDocument();
|
expect(dropdown).toBeInTheDocument();
|
||||||
await user.click(dropdown);
|
await user.click(dropdown);
|
||||||
|
|
||||||
const unenrollOption = screen.getByRole('button', { name: messages.unenroll.defaultMessage });
|
const unenrollOption = screen.getByRole('button', { name: messages.unenroll.defaultMessage });
|
||||||
expect(unenrollOption).toBeInTheDocument();
|
expect(unenrollOption).toBeInTheDocument();
|
||||||
expect(unenrollOption).toHaveAttribute('aria-disabled', 'true');
|
expect(unenrollOption).toHaveAttribute('aria-disabled', 'true');
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineMessages } from '@openedx/frontend-base';
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
unenroll: {
|
unenroll: {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user