Compare commits
13 Commits
release/te
...
jodybailey
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1e4e2d414 | ||
|
|
f4c27f02ba | ||
|
|
55a647bb5b | ||
|
|
c3b98d954e | ||
|
|
91a694736a | ||
|
|
7fe3bf7ab8 | ||
|
|
68db9a9829 | ||
|
|
bdf3870808 | ||
|
|
69e7c71885 | ||
|
|
cd7650ab42 | ||
|
|
7bd3452dc3 | ||
|
|
be1e1bf7d9 | ||
|
|
807d9f70b8 |
3
.env
3
.env
@@ -32,6 +32,7 @@ ENTERPRISE_MARKETING_UTM_SOURCE=''
|
|||||||
ENTERPRISE_MARKETING_UTM_CAMPAIGN=''
|
ENTERPRISE_MARKETING_UTM_CAMPAIGN=''
|
||||||
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM=''
|
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM=''
|
||||||
LEARNING_BASE_URL=''
|
LEARNING_BASE_URL=''
|
||||||
|
ZENDESK_KEY=''
|
||||||
HOTJAR_APP_ID=''
|
HOTJAR_APP_ID=''
|
||||||
HOTJAR_VERSION='6'
|
HOTJAR_VERSION='6'
|
||||||
HOTJAR_DEBUG=''
|
HOTJAR_DEBUG=''
|
||||||
@@ -39,5 +40,3 @@ ACCOUNT_SETTINGS_URL=''
|
|||||||
ACCOUNT_PROFILE_URL=''
|
ACCOUNT_PROFILE_URL=''
|
||||||
ENABLE_NOTICES=''
|
ENABLE_NOTICES=''
|
||||||
CAREER_LINK_URL=''
|
CAREER_LINK_URL=''
|
||||||
ENABLE_EDX_PERSONAL_DASHBOARD=false
|
|
||||||
ENABLE_PROGRAMS=false
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ LOGOUT_URL='http://localhost:18000/logout'
|
|||||||
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
|
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
|
||||||
LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.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
|
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
|
||||||
|
LOGO_POWERED_BY_OPEN_EDX_URL_SVG=https://edx-cdn.org/v3/stage/open-edx-tag.svg
|
||||||
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
||||||
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
||||||
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||||
@@ -20,7 +21,7 @@ LMS_CLIENT_ID='login-service-client-id'
|
|||||||
SEGMENT_KEY=''
|
SEGMENT_KEY=''
|
||||||
FEATURE_FLAGS={}
|
FEATURE_FLAGS={}
|
||||||
MARKETING_SITE_BASE_URL='http://localhost:18000'
|
MARKETING_SITE_BASE_URL='http://localhost:18000'
|
||||||
SUPPORT_URL=''
|
SUPPORT_URL='http://localhost:18000/support'
|
||||||
CONTACT_URL='http://localhost:18000/contact'
|
CONTACT_URL='http://localhost:18000/contact'
|
||||||
OPEN_SOURCE_URL='http://localhost:18000/openedx'
|
OPEN_SOURCE_URL='http://localhost:18000/openedx'
|
||||||
TERMS_OF_SERVICE_URL='http://localhost:18000/terms-of-service'
|
TERMS_OF_SERVICE_URL='http://localhost:18000/terms-of-service'
|
||||||
@@ -38,6 +39,7 @@ ENTERPRISE_MARKETING_UTM_CAMPAIGN='example.com Referral'
|
|||||||
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='Footer'
|
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='Footer'
|
||||||
LEARNING_BASE_URL='http://localhost:2000'
|
LEARNING_BASE_URL='http://localhost:2000'
|
||||||
SESSION_COOKIE_DOMAIN='localhost'
|
SESSION_COOKIE_DOMAIN='localhost'
|
||||||
|
ZENDESK_KEY=''
|
||||||
HOTJAR_APP_ID=''
|
HOTJAR_APP_ID=''
|
||||||
HOTJAR_VERSION='6'
|
HOTJAR_VERSION='6'
|
||||||
HOTJAR_DEBUG=''
|
HOTJAR_DEBUG=''
|
||||||
@@ -45,5 +47,3 @@ ACCOUNT_SETTINGS_URL='http://localhost:1997'
|
|||||||
ACCOUNT_PROFILE_URL='http://localhost:1995'
|
ACCOUNT_PROFILE_URL='http://localhost:1995'
|
||||||
ENABLE_NOTICES=''
|
ENABLE_NOTICES=''
|
||||||
CAREER_LINK_URL=''
|
CAREER_LINK_URL=''
|
||||||
ENABLE_EDX_PERSONAL_DASHBOARD=false
|
|
||||||
ENABLE_PROGRAMS=false
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ LOGOUT_URL='http://localhost:18000/logout'
|
|||||||
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
|
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
|
||||||
LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.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
|
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
|
||||||
|
LOGO_POWERED_BY_OPEN_EDX_URL_SVG=https://edx-cdn.org/v3/stage/open-edx-tag.svg
|
||||||
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
||||||
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
||||||
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||||
@@ -20,7 +21,7 @@ LMS_CLIENT_ID='login-service-client-id'
|
|||||||
SEGMENT_KEY=''
|
SEGMENT_KEY=''
|
||||||
FEATURE_FLAGS={}
|
FEATURE_FLAGS={}
|
||||||
MARKETING_SITE_BASE_URL='http://localhost:18000'
|
MARKETING_SITE_BASE_URL='http://localhost:18000'
|
||||||
SUPPORT_URL=''
|
SUPPORT_URL='http://localhost:18000/support'
|
||||||
CONTACT_URL='http://localhost:18000/contact'
|
CONTACT_URL='http://localhost:18000/contact'
|
||||||
OPEN_SOURCE_URL='http://localhost:18000/openedx'
|
OPEN_SOURCE_URL='http://localhost:18000/openedx'
|
||||||
TERMS_OF_SERVICE_URL='http://localhost:18000/terms-of-service'
|
TERMS_OF_SERVICE_URL='http://localhost:18000/terms-of-service'
|
||||||
@@ -37,6 +38,7 @@ ENTERPRISE_MARKETING_UTM_SOURCE='example.com'
|
|||||||
ENTERPRISE_MARKETING_UTM_CAMPAIGN='example.com Referral'
|
ENTERPRISE_MARKETING_UTM_CAMPAIGN='example.com Referral'
|
||||||
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='Footer'
|
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='Footer'
|
||||||
LEARNING_BASE_URL='http://localhost:2000'
|
LEARNING_BASE_URL='http://localhost:2000'
|
||||||
|
ZENDESK_KEY='test-zendesk-key'
|
||||||
HOTJAR_APP_ID='hot-jar-app-id'
|
HOTJAR_APP_ID='hot-jar-app-id'
|
||||||
HOTJAR_VERSION='6'
|
HOTJAR_VERSION='6'
|
||||||
HOTJAR_DEBUG=''
|
HOTJAR_DEBUG=''
|
||||||
@@ -44,5 +46,3 @@ ACCOUNT_SETTINGS_URL='http://account-settings-url.test'
|
|||||||
ACCOUNT_PROFILE_URL='http://account-profile-url.test'
|
ACCOUNT_PROFILE_URL='http://account-profile-url.test'
|
||||||
ENABLE_NOTICES=''
|
ENABLE_NOTICES=''
|
||||||
CAREER_LINK_URL=''
|
CAREER_LINK_URL=''
|
||||||
ENABLE_EDX_PERSONAL_DASHBOARD=true
|
|
||||||
ENABLE_PROGRAMS=false
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
const { createConfig } = require('@openedx/frontend-build');
|
const { createConfig } = require('@edx/frontend-build');
|
||||||
|
|
||||||
const config = createConfig('eslint', {
|
const config = createConfig('eslint', {
|
||||||
rules: {
|
rules: {
|
||||||
'import/no-named-as-default': 'off',
|
'import/no-named-as-default': 'off',
|
||||||
'import/no-named-as-default-member': 'off',
|
'import/no-named-as-default-member': 'off',
|
||||||
'import/no-self-import': 'off',
|
'import/no-self-import': 'off',
|
||||||
'import/no-import-module-exports': 'off',
|
|
||||||
'spaced-comment': ['error', 'always', { 'block': { 'exceptions': ['*'] } }],
|
'spaced-comment': ['error', 'always', { 'block': { 'exceptions': ['*'] } }],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
9
.github/CODEOWNERS
vendored
9
.github/CODEOWNERS
vendored
@@ -1 +1,8 @@
|
|||||||
* @openedx/2U-aperture
|
# Root app is developed and owned by Aurora
|
||||||
|
* @edx/content-aurora
|
||||||
|
|
||||||
|
# WIDGETS and experiments are developed and owned by separate teams below
|
||||||
|
|
||||||
|
# Recommendations panel
|
||||||
|
/src/widgets/RecommendationsPanel @edx/vanguards
|
||||||
|
/src/widgets/LookingForChallengeWidget @edx/vanguards
|
||||||
|
|||||||
7
.github/dependabot.yml
vendored
7
.github/dependabot.yml
vendored
@@ -1,7 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
# Adding new check for github-actions
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
33
.github/renovate.json
vendored
33
.github/renovate.json
vendored
@@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [
|
|
||||||
"config:base",
|
|
||||||
"schedule:weekly",
|
|
||||||
":automergeLinters",
|
|
||||||
":automergeMinor",
|
|
||||||
":automergeTesters",
|
|
||||||
":enableVulnerabilityAlerts",
|
|
||||||
":rebaseStalePrs",
|
|
||||||
":semanticCommits",
|
|
||||||
":updateNotScheduled"
|
|
||||||
],
|
|
||||||
"packageRules": [
|
|
||||||
{
|
|
||||||
"matchDepTypes": [
|
|
||||||
"devDependencies"
|
|
||||||
],
|
|
||||||
"matchUpdateTypes": [
|
|
||||||
"lockFileMaintenance",
|
|
||||||
"minor",
|
|
||||||
"patch",
|
|
||||||
"pin"
|
|
||||||
],
|
|
||||||
"automerge": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"matchPackagePatterns": ["@edx", "@openedx"],
|
|
||||||
"matchUpdateTypes": ["minor", "patch"],
|
|
||||||
"automerge": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timezone": "America/New_York"
|
|
||||||
}
|
|
||||||
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
@@ -10,16 +10,18 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup Nodejs Env
|
||||||
|
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Setup Nodejs
|
- name: Setup Nodejs
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version: ${{ env.NODE_VER }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
@@ -37,7 +39,19 @@ jobs:
|
|||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
- name: Run Coverage
|
- name: Run Coverage
|
||||||
uses: codecov/codecov-action@v5
|
uses: codecov/codecov-action@v3
|
||||||
|
|
||||||
|
- name: Send failure notification
|
||||||
|
if: ${{ failure() }}
|
||||||
|
uses: dawidd6/action-send-mail@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
server_address: email-smtp.us-east-1.amazonaws.com
|
||||||
fail_ci_if_error: true
|
server_port: 465
|
||||||
|
username: ${{ secrets.EDX_SMTP_USERNAME }}
|
||||||
|
password: ${{ secrets.EDX_SMTP_PASSWORD }}
|
||||||
|
subject: Upgrade python requirements workflow failed in ${{github.repository}}
|
||||||
|
to: masters-grades@edx.org
|
||||||
|
from: github-actions <github-actions@edx.org>
|
||||||
|
body: Upgrade python requirements workflow in ${{github.repository}} failed!
|
||||||
|
For details see "github.com/${{ github.repository }}/actions/runs/${{ github.run_id
|
||||||
|
}}"
|
||||||
|
|||||||
35
.github/workflows/npm-publish.yml
vendored
Normal file
35
.github/workflows/npm-publish.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
name: Release CI
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Release
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Nodejs Env
|
||||||
|
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VER }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Create Build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Release Package
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_GITHUB_TOKEN }}
|
||||||
|
NPM_TOKEN: ${{ secrets.SEMANTIC_RELEASE_NPM_TOKEN }}
|
||||||
|
run: npm semantic-release
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,5 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.eslintcache
|
.eslintcache
|
||||||
env.config.*
|
|
||||||
node_modules
|
node_modules
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
coverage
|
coverage
|
||||||
@@ -26,4 +25,3 @@ module.config.js
|
|||||||
### transifex ###
|
### transifex ###
|
||||||
src/i18n/transifex_input.json
|
src/i18n/transifex_input.json
|
||||||
temp
|
temp
|
||||||
src/i18n/messages
|
|
||||||
4
.husky/pre-push
Executable file
4
.husky/pre-push
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npm run lint
|
||||||
27
.releaserc
Normal file
27
.releaserc
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"branch": "master",
|
||||||
|
"tagFormat": "v${version}",
|
||||||
|
"verifyConditions": [
|
||||||
|
"@semantic-release/npm",
|
||||||
|
{
|
||||||
|
"path": "@semantic-release/github",
|
||||||
|
"assets": {
|
||||||
|
"path": "dist/*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"analyzeCommits": "@semantic-release/commit-analyzer",
|
||||||
|
"generateNotes": "@semantic-release/release-notes-generator",
|
||||||
|
"prepare": "@semantic-release/npm",
|
||||||
|
"publish": [
|
||||||
|
"@semantic-release/npm",
|
||||||
|
{
|
||||||
|
"path": "@semantic-release/github",
|
||||||
|
"assets": {
|
||||||
|
"path": "dist/*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"success": [],
|
||||||
|
"fail": []
|
||||||
|
}
|
||||||
9
.tx/config
Normal file
9
.tx/config
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[main]
|
||||||
|
host = https://www.transifex.com
|
||||||
|
|
||||||
|
[o:open-edx:p:edx-platform:r:frontend-app-learner-dashboard]
|
||||||
|
file_filter = src/i18n/messages/<lang>.json
|
||||||
|
source_file = src/i18n/transifex_input.json
|
||||||
|
source_lang = en
|
||||||
|
type = KEYVALUEJSON
|
||||||
|
|
||||||
28
Makefile
28
Makefile
@@ -1,6 +1,8 @@
|
|||||||
npm-install-%: ## install specified % npm package
|
npm-install-%: ## install specified % npm package
|
||||||
npm install $* --save-dev
|
npm install $* --save-dev
|
||||||
git add package.json
|
git add package.json
|
||||||
|
export TRANSIFEX_RESOURCE = frontend-app-learner-dashboard
|
||||||
|
transifex_langs = "ar,fr,fr_CA,es_419,pt_BR,zh_CN"
|
||||||
|
|
||||||
intl_imports = ./node_modules/.bin/intl-imports.js
|
intl_imports = ./node_modules/.bin/intl-imports.js
|
||||||
transifex_utils = ./node_modules/.bin/transifex-utils.js
|
transifex_utils = ./node_modules/.bin/transifex-utils.js
|
||||||
@@ -8,7 +10,7 @@ i18n = ./src/i18n
|
|||||||
transifex_input = $(i18n)/transifex_input.json
|
transifex_input = $(i18n)/transifex_input.json
|
||||||
|
|
||||||
# This directory must match .babelrc .
|
# This directory must match .babelrc .
|
||||||
transifex_temp = ./temp/babel-plugin-formatjs
|
transifex_temp = ./temp/babel-plugin-react-intl
|
||||||
|
|
||||||
NPM_TESTS=build i18n_extract lint test
|
NPM_TESTS=build i18n_extract lint test
|
||||||
|
|
||||||
@@ -40,17 +42,33 @@ detect_changed_source_translations:
|
|||||||
# Checking for changed translations...
|
# Checking for changed translations...
|
||||||
git diff --exit-code $(i18n)
|
git diff --exit-code $(i18n)
|
||||||
|
|
||||||
|
# Pushes translations to Transifex. You must run make extract_translations first.
|
||||||
|
push_translations:
|
||||||
|
# Pushing strings to Transifex...
|
||||||
|
tx push -s
|
||||||
|
# Fetching hashes from Transifex...
|
||||||
|
./node_modules/@edx/reactifex/bash_scripts/get_hashed_strings_v3.sh
|
||||||
|
# Writing out comments to file...
|
||||||
|
$(transifex_utils) $(transifex_temp) --comments --v3-scripts-path
|
||||||
|
# Pushing comments to Transifex...
|
||||||
|
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
|
||||||
|
|
||||||
|
ifeq ($(OPENEDX_ATLAS_PULL),)
|
||||||
|
# Pulls translations from Transifex.
|
||||||
|
pull_translations:
|
||||||
|
tx pull -t -f --mode reviewed --languages=$(transifex_langs)
|
||||||
|
else
|
||||||
|
# Experimental: OEP-58 Pulls translations using atlas
|
||||||
pull_translations:
|
pull_translations:
|
||||||
rm -rf src/i18n/messages
|
rm -rf src/i18n/messages
|
||||||
mkdir src/i18n/messages
|
mkdir src/i18n/messages
|
||||||
cd src/i18n/messages \
|
cd src/i18n/messages \
|
||||||
&& atlas pull $(ATLAS_OPTIONS) \
|
&& atlas pull --filter=$(transifex_langs) \
|
||||||
translations/frontend-platform/src/i18n/messages:frontend-platform \
|
|
||||||
translations/paragon/src/i18n/messages:paragon \
|
|
||||||
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
|
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-platform paragon frontend-component-footer frontend-app-learner-dashboard
|
$(intl_imports) frontend-component-footer frontend-app-learner-dashboard
|
||||||
|
endif
|
||||||
|
|
||||||
# This target is used by CI.
|
# This target is used by CI.
|
||||||
validate-no-uncommitted-package-lock-changes:
|
validate-no-uncommitted-package-lock-changes:
|
||||||
|
|||||||
26
README.md
Normal file
26
README.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# frontend-app-learner-dashboard
|
||||||
|
|
||||||
|
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 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/footer components.
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
To start the MFE and enable the feature in LMS:
|
||||||
|
|
||||||
|
1. Start the MFE with `npm run start`. Take a note of the path/port (defaults to `http://localhost:1996`).
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
A core goal of this app is to provide a clean experimentation interface. To promote this end, we have provided a silo'ed code directory at `src/widgets` in which contributors should add their custom widget components. In order to ensure our ability to maintain the code stability of the app, the code for these widgets should be strictly contained within the bounds of that directory.
|
||||||
|
|
||||||
|
Once written, the widgets can be configured into one of our widget containers at `src/containers/WidgetContainers`. This can include conditional logic, as well as Optimizely triggers. It is important to note that our integration tests will isolate and ignore these containers, and thus testing your widget is the response of the creator/maintainer of the widget itself.
|
||||||
|
|
||||||
|
Some guidelines for writing widgets:
|
||||||
|
* Code for the widget should be strictly confined to the `src/widgets` directory.
|
||||||
|
* You can load data from the redux store, but should not add or modify fields in that structure.
|
||||||
|
* Network events should be managed in component hooks, though can use our `data/constants/requests:requestStates` for ease of tracking the request states.
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
* [Learner Home project info at the Open edX Wiki](https://openedx.atlassian.net/wiki/spaces/OEPM/pages/3575906333/Learner+Home)
|
||||||
107
README.rst
107
README.rst
@@ -1,107 +0,0 @@
|
|||||||
|license-badge| |status-badge| |ci-badge| |codecov-badge|
|
|
||||||
|
|
||||||
.. |license-badge| image:: https://img.shields.io/github/license/openedx/frontend-app-learner-dashboard.svg
|
|
||||||
:target: https://github.com/openedx/frontend-app-learner-dashboard/blob/master/LICENSE
|
|
||||||
:alt: License
|
|
||||||
.. |status-badge| image:: https://img.shields.io/badge/Status-Maintained-brightgreen
|
|
||||||
:alt: Maintained
|
|
||||||
.. |ci-badge| image:: https://github.com/openedx/frontend-app-learner-dashboard/actions/workflows/ci.yml/badge.svg
|
|
||||||
:target: https://github.com/openedx/frontend-app-learner-dashboard/actions/workflows/ci.yml
|
|
||||||
:alt: Continuous Integration
|
|
||||||
.. |codecov-badge| image:: https://codecov.io/github/openedx/frontend-app-learner-dashboard/coverage.svg?branch=master
|
|
||||||
:target: https://app.codecov.io/github/openedx/frontend-app-learner-dashboard?branch=master
|
|
||||||
:alt: Codecov
|
|
||||||
|
|
||||||
frontend-app-learner-dashboard
|
|
||||||
==============================
|
|
||||||
|
|
||||||
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
|
|
||||||
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/footer components.
|
|
||||||
|
|
||||||
Quickstart
|
|
||||||
----------
|
|
||||||
|
|
||||||
To start the MFE and enable the feature in LMS:
|
|
||||||
|
|
||||||
1. Start the MFE with ``npm run start``. Take a note of the path/port (defaults to ``http://localhost:1996``).
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Plugins
|
|
||||||
-------
|
|
||||||
This MFE can be customized using `Frontend Plugin Framework <https://github.com/openedx/frontend-plugin-framework>`_.
|
|
||||||
|
|
||||||
The parts of this MFE that can be customized in that manner are documented `here </src/plugin-slots>`_.
|
|
||||||
|
|
||||||
Contributing
|
|
||||||
------------
|
|
||||||
|
|
||||||
A core goal of this app is to provide a clean experimentation interface. To promote this end, we have provided a
|
|
||||||
silo'ed code directory at ``src/widgets`` in which contributors should add their custom widget components. In order to
|
|
||||||
ensure our ability to maintain the code stability of the app, the code for these widgets should be strictly contained
|
|
||||||
within the bounds of that directory.
|
|
||||||
|
|
||||||
Once written, the widgets can be configured into one of our widget containers at ``src/containers/WidgetContainers``.
|
|
||||||
This can include conditional logic, as well as Optimizely triggers. It is important to note that our integration tests
|
|
||||||
will isolate and ignore these containers, and thus testing your widget is the response of the creator/maintainer of the
|
|
||||||
widget itself.
|
|
||||||
|
|
||||||
Some guidelines for writing widgets:
|
|
||||||
|
|
||||||
* Code for the widget should be strictly confined to the ``src/widgets`` directory.
|
|
||||||
* You can load data from the redux store, but should not add or modify fields in that structure.
|
|
||||||
* Network events should be managed in component hooks, though can use our ``data/constants/requests:requestStates`` for
|
|
||||||
ease of tracking the request states.
|
|
||||||
|
|
||||||
License
|
|
||||||
-------
|
|
||||||
|
|
||||||
The code in this repository is licensed under the AGPLv3 unless otherwise noted.
|
|
||||||
|
|
||||||
Please see the `license`_ for more info.
|
|
||||||
|
|
||||||
.. _license: https://github.com/openedx/frontend-app-learner-dashboard/blob/master/LICENSE
|
|
||||||
|
|
||||||
Getting Help
|
|
||||||
------------
|
|
||||||
|
|
||||||
If you're having trouble, we have discussion forums at https://discuss.openedx.org where you can connect with others in
|
|
||||||
the community.
|
|
||||||
|
|
||||||
Our real-time conversations are on Slack. You can request a `Slack invitation`_, then join our
|
|
||||||
`community Slack workspace`_. Because this is a frontend repository, the best place to discuss it would be in the
|
|
||||||
`#wg-frontend channel`_.
|
|
||||||
|
|
||||||
For anything non-trivial, the best path is to open an issue in this repository with as many details about the issue you
|
|
||||||
are facing as you can provide.
|
|
||||||
|
|
||||||
https://github.com/openedx/frontend-app-learner-dashboard/issues
|
|
||||||
|
|
||||||
For more information about these options, see the `Getting Help`_ page.
|
|
||||||
|
|
||||||
.. _Slack invitation: https://openedx.org/slack
|
|
||||||
.. _community Slack workspace: https://openedx.slack.com/
|
|
||||||
.. _#wg-frontend channel: https://openedx.slack.com/archives/C04BM6YC7A6
|
|
||||||
.. _Getting Help: https://openedx.org/community/connect
|
|
||||||
|
|
||||||
Resources
|
|
||||||
---------
|
|
||||||
|
|
||||||
Additional info about the Learner Home MFE project can be found on the `Open edX Wiki`_.
|
|
||||||
|
|
||||||
.. _Open edX Wiki: https://openedx.atlassian.net/wiki/spaces/OEPM/pages/3575906333/Learner+Home
|
|
||||||
|
|
||||||
The Open edX Code of Conduct
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
All community members are expected to follow the `Open edX Code of Conduct`_.
|
|
||||||
|
|
||||||
.. _Open edX Code of Conduct: https://openedx.org/code-of-conduct/
|
|
||||||
|
|
||||||
Reporting Security Issues
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
Please do not report security issues in public. Please email security@openedx.org.
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# This file records information about this repo. Its use is described in OEP-55:
|
|
||||||
# https://open-edx-proposals.readthedocs.io/en/latest/processes/oep-0055-proc-project-maintainers.html
|
|
||||||
apiVersion: backstage.io/v1alpha1
|
|
||||||
kind: Component
|
|
||||||
metadata:
|
|
||||||
name: 'frontend-app-learner-dashboard'
|
|
||||||
description: 'The Microfrontend for the course listing interface allowing learners to view and act upon enrollments.'
|
|
||||||
links:
|
|
||||||
- url: 'https://github.com/openedx/frontend-app-learner-dashboard/blob/master/README.rst'
|
|
||||||
title: 'Documentation'
|
|
||||||
icon: 'Article'
|
|
||||||
annotations:
|
|
||||||
# (Optional) Annotation keys and values can be whatever you want.
|
|
||||||
# We use it in Open edX repos to have a comma-separated list of GitHub user
|
|
||||||
# names that might be interested in changes to the architecture of this
|
|
||||||
# component.
|
|
||||||
openedx.org/arch-interest-groups: ""
|
|
||||||
# This can be multiple comma-separated projects.
|
|
||||||
openedx.org/add-to-projects: "openedx:23"
|
|
||||||
openedx.org/release: "master"
|
|
||||||
spec:
|
|
||||||
type: 'service'
|
|
||||||
lifecycle: 'production'
|
|
||||||
owner: 2U-aperture
|
|
||||||
# (Optional) An array of different components or resources.
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
/*
|
|
||||||
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',
|
|
||||||
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,4 +1,4 @@
|
|||||||
const { createConfig } = require('@openedx/frontend-build');
|
const { createConfig } = require('@edx/frontend-build');
|
||||||
|
|
||||||
module.exports = createConfig('jest', {
|
module.exports = createConfig('jest', {
|
||||||
setupFilesAfterEnv: [
|
setupFilesAfterEnv: [
|
||||||
@@ -6,6 +6,9 @@ module.exports = createConfig('jest', {
|
|||||||
'<rootDir>/src/setupTest.jsx',
|
'<rootDir>/src/setupTest.jsx',
|
||||||
],
|
],
|
||||||
modulePaths: ['<rootDir>/src/'],
|
modulePaths: ['<rootDir>/src/'],
|
||||||
|
snapshotSerializers: [
|
||||||
|
'enzyme-to-json/serializer',
|
||||||
|
],
|
||||||
coveragePathIgnorePatterns: [
|
coveragePathIgnorePatterns: [
|
||||||
'src/segment.js',
|
'src/segment.js',
|
||||||
'src/postcss.config.js',
|
'src/postcss.config.js',
|
||||||
|
|||||||
9
openedx.yaml
Normal file
9
openedx.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# This file describes this Open edX repo, as described in OEP-2:
|
||||||
|
# http://open-edx-proposals.readthedocs.io/en/latest/oeps/oep-0002.html#specification
|
||||||
|
|
||||||
|
tags:
|
||||||
|
- frontend-app
|
||||||
|
- masters
|
||||||
|
oeps:
|
||||||
|
oep-2: true # Repository metadata
|
||||||
|
openedx-release: {ref: master}
|
||||||
35556
package-lock.json
generated
35556
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
95
package.json
95
package.json
@@ -6,21 +6,18 @@
|
|||||||
"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"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
|
||||||
"extends @edx/browserslist-config"
|
|
||||||
],
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "fedx-scripts webpack",
|
"build": "fedx-scripts webpack",
|
||||||
"i18n_extract": "fedx-scripts formatjs extract",
|
"i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null",
|
||||||
"lint": "fedx-scripts eslint --ext .jsx,.js src/",
|
"lint": "fedx-scripts eslint --ext .jsx,.js src/",
|
||||||
"lint-fix": "fedx-scripts eslint --fix --ext .jsx,.js src/",
|
"lint-fix": "fedx-scripts eslint --fix --ext .jsx,.js src/",
|
||||||
"semantic-release": "semantic-release",
|
"semantic-release": "semantic-release",
|
||||||
"start": "fedx-scripts webpack-dev-server --progress",
|
"start": "fedx-scripts webpack-dev-server --progress",
|
||||||
"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": "TZ=GMT fedx-scripts jest --coverage --passWithNoTests",
|
"test": "TZ=GMT fedx-scripts jest --coverage --passWithNoTests",
|
||||||
"quality": "npm run lint-fix && npm run test",
|
"quality": "npm run lint-fix && npm run test",
|
||||||
"watch-tests": "jest --watch",
|
"watch-tests": "jest --watch",
|
||||||
"snapshot": "fedx-scripts jest --updateSnapshot"
|
"snapshot": "fedx-scripts jest --updateSnapshot",
|
||||||
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"author": "edX",
|
"author": "edX",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
@@ -29,59 +26,71 @@
|
|||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
|
"@edx/brand": "npm:@edx/brand-edx.org@^2.0.3",
|
||||||
"@edx/frontend-component-footer": "^14.6.0",
|
"@edx/browserslist-config": "^1.1.0",
|
||||||
"@edx/frontend-component-header": "^6.2.0",
|
"@edx/frontend-component-footer": "^12.0.0",
|
||||||
"@edx/frontend-enterprise-hotjar": "7.2.0",
|
"@edx/frontend-enterprise-hotjar": "^1.2.0",
|
||||||
"@edx/frontend-platform": "^8.3.1",
|
"@edx/frontend-platform": "^4.2.0",
|
||||||
"@edx/openedx-atlas": "^0.6.0",
|
"@edx/paragon": "^20.32.0",
|
||||||
"@edx/react-unit-test-utils": "^4.0.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.1.15",
|
||||||
"@openedx/frontend-plugin-framework": "^1.7.0",
|
"@redux-beacon/segment": "^1.1.0",
|
||||||
"@openedx/paragon": "^22.16.0",
|
"@reduxjs/toolkit": "^1.6.1",
|
||||||
"@redux-devtools/extension": "3.3.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"@reduxjs/toolkit": "^2.0.0",
|
"axios": "^0.21.4",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"core-js": "3.40.0",
|
"core-js": "3.16.2",
|
||||||
"filesize": "^10.0.0",
|
"dompurify": "^2.3.1",
|
||||||
|
"email-prop-type": "^3.0.1",
|
||||||
|
"enzyme": "^3.11.0",
|
||||||
|
"enzyme-to-json": "^3.6.2",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
|
"filesize": "^8.0.6",
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
"history": "5.3.0",
|
"history": "5.0.1",
|
||||||
|
"html-react-parser": "^1.3.0",
|
||||||
|
"jest": "^26.6.3",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"prop-types": "15.8.1",
|
"prop-types": "15.7.2",
|
||||||
"query-string": "7.1.3",
|
"query-string": "7.0.1",
|
||||||
"react": "^18.3.1",
|
"react": "^16.14.0",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^16.14.0",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-intl": "6.8.9",
|
"react-intl": "^5.20.9",
|
||||||
|
"react-pdf": "^5.5.0",
|
||||||
"react-redux": "^7.2.4",
|
"react-redux": "^7.2.4",
|
||||||
"react-router-dom": "6.29.0",
|
"react-router-dom": "5.3.3",
|
||||||
"react-share": "^4.4.0",
|
"react-share": "^4.4.0",
|
||||||
"redux": "4.2.1",
|
"react-zendesk": "^0.1.13",
|
||||||
|
"redux": "4.1.1",
|
||||||
|
"redux-beacon": "^2.1.0",
|
||||||
|
"redux-devtools-extension": "2.13.9",
|
||||||
"redux-logger": "3.0.6",
|
"redux-logger": "3.0.6",
|
||||||
"redux-thunk": "2.4.2",
|
"redux-thunk": "2.3.0",
|
||||||
"regenerator-runtime": "^0.14.0",
|
"regenerator-runtime": "^0.13.9",
|
||||||
"reselect": "^4.0.0",
|
"reselect": "^4.0.0",
|
||||||
"universal-cookie": "^4.0.4",
|
"universal-cookie": "^4.0.4",
|
||||||
"util": "^0.12.4"
|
"util": "^0.12.4",
|
||||||
|
"whatwg-fetch": "^3.6.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@edx/browserslist-config": "^1.3.0",
|
"@edx/frontend-build": "12.8.27",
|
||||||
"@edx/reactifex": "^2.1.1",
|
"@edx/reactifex": "^2.1.1",
|
||||||
"@openedx/frontend-build": "^14.3.3",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/react": "^12.1.0",
|
||||||
"@testing-library/react": "^16.2.0",
|
"axios-mock-adapter": "^1.20.0",
|
||||||
"copy-webpack-plugin": "^12.0.0",
|
"copy-webpack-plugin": "^11.0.0",
|
||||||
|
"enzyme-adapter-react-16": "^1.15.6",
|
||||||
|
"fetch-mock": "^9.11.0",
|
||||||
|
"husky": "^7.0.0",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "^29.7.0",
|
"jest-expect-message": "^1.0.2",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"react-dev-utils": "^11.0.4",
|
||||||
"jest-expect-message": "^1.1.3",
|
"react-test-renderer": "^16.14.0",
|
||||||
"jest-when": "^3.6.0",
|
"redux-mock-store": "^1.5.4",
|
||||||
"react-dev-utils": "^12.0.0",
|
"semantic-release": "^20.1.3"
|
||||||
"react-test-renderer": "^18.3.1",
|
|
||||||
"redux-mock-store": "^1.5.4"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en-us" dir="ltr">
|
<html lang="en-us" dir="ltr">
|
||||||
<head>
|
<head>
|
||||||
|
<title>Learner Dashboard | <%= process.env.SITE_NAME %></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">
|
||||||
|
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
52
src/App.jsx
52
src/App.jsx
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
@@ -6,8 +7,8 @@ import { logError } from '@edx/frontend-platform/logging';
|
|||||||
import { initializeHotjar } from '@edx/frontend-enterprise-hotjar';
|
import { initializeHotjar } from '@edx/frontend-enterprise-hotjar';
|
||||||
|
|
||||||
import { ErrorPage, AppContext } from '@edx/frontend-platform/react';
|
import { ErrorPage, AppContext } from '@edx/frontend-platform/react';
|
||||||
import { FooterSlot } from '@edx/frontend-component-footer';
|
import Footer from '@edx/frontend-component-footer';
|
||||||
import { Alert } from '@openedx/paragon';
|
import { Alert } from '@edx/paragon';
|
||||||
|
|
||||||
import { RequestKeys } from 'data/constants/requests';
|
import { RequestKeys } from 'data/constants/requests';
|
||||||
import store from 'data/store';
|
import store from 'data/store';
|
||||||
@@ -17,16 +18,15 @@ import {
|
|||||||
} from 'data/redux';
|
} from 'data/redux';
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import Dashboard from 'containers/Dashboard';
|
import Dashboard from 'containers/Dashboard';
|
||||||
|
import ZendeskFab from 'components/ZendeskFab';
|
||||||
|
|
||||||
import track from 'tracking';
|
import track from 'tracking';
|
||||||
|
|
||||||
import fakeData from 'data/services/lms/fakeData/courses';
|
import fakeData from 'data/services/lms/fakeData/courses';
|
||||||
|
import LearnerDashboardHeader from './containers/LearnerDashboardHeader';
|
||||||
|
|
||||||
import AppWrapper from 'containers/WidgetContainers/AppWrapper';
|
|
||||||
import LearnerDashboardHeader from 'containers/LearnerDashboardHeader';
|
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
import './App.scss';
|
import './App.scss';
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
@@ -41,7 +41,7 @@ export const App = () => {
|
|||||||
const loadData = reduxHooks.useLoadData();
|
const loadData = reduxHooks.useLoadData();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (authenticatedUser?.administrator || getConfig().NODE_ENV === 'development') {
|
if (authenticatedUser?.administrator || process.env.NODE_ENV === 'development') {
|
||||||
window.loadEmptyData = () => {
|
window.loadEmptyData = () => {
|
||||||
loadData({ ...fakeData.globalData, courses: [] });
|
loadData({ ...fakeData.globalData, courses: [] });
|
||||||
};
|
};
|
||||||
@@ -59,12 +59,12 @@ export const App = () => {
|
|||||||
window.actions = actions;
|
window.actions = actions;
|
||||||
window.track = track;
|
window.track = track;
|
||||||
}
|
}
|
||||||
if (getConfig().HOTJAR_APP_ID) {
|
if (process.env.HOTJAR_APP_ID) {
|
||||||
try {
|
try {
|
||||||
initializeHotjar({
|
initializeHotjar({
|
||||||
hotjarId: getConfig().HOTJAR_APP_ID,
|
hotjarId: process.env.HOTJAR_APP_ID,
|
||||||
hotjarVersion: getConfig().HOTJAR_VERSION,
|
hotjarVersion: process.env.HOTJAR_VERSION,
|
||||||
hotjarDebug: !!getConfig().HOTJAR_DEBUG,
|
hotjarDebug: !!process.env.HOTJAR_DEBUG,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error);
|
logError(error);
|
||||||
@@ -72,28 +72,24 @@ export const App = () => {
|
|||||||
}
|
}
|
||||||
}, [authenticatedUser, loadData]);
|
}, [authenticatedUser, loadData]);
|
||||||
return (
|
return (
|
||||||
<>
|
<Router>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{formatMessage(messages.pageTitle)}</title>
|
<title>{formatMessage(messages.pageTitle)}</title>
|
||||||
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
|
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div>
|
<div>
|
||||||
<AppWrapper>
|
<LearnerDashboardHeader />
|
||||||
<LearnerDashboardHeader />
|
<main>
|
||||||
<main id="main">
|
{hasNetworkFailure
|
||||||
{hasNetworkFailure
|
? (
|
||||||
? (
|
<Alert variant="danger">
|
||||||
<Alert variant="danger">
|
<ErrorPage message={formatMessage(messages.errorMessage, { supportEmail })} />
|
||||||
<ErrorPage message={formatMessage(messages.errorMessage, { supportEmail })} />
|
</Alert>
|
||||||
</Alert>
|
) : (<Dashboard />)}
|
||||||
) : (
|
</main>
|
||||||
<Dashboard />
|
<Footer logo={process.env.LOGO_POWERED_BY_OPEN_EDX_URL_SVG} />
|
||||||
)}
|
<ZendeskFab />
|
||||||
</main>
|
|
||||||
</AppWrapper>
|
|
||||||
<FooterSlot />
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</Router>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// frontend-app-*/src/index.scss
|
// frontend-app-*/src/index.scss
|
||||||
@import "~@edx/brand/paragon/fonts";
|
@import "~@edx/brand/paragon/fonts";
|
||||||
@import "~@edx/brand/paragon/variables";
|
@import "~@edx/brand/paragon/variables";
|
||||||
@import "~@openedx/paragon/scss/core/core";
|
@import "~@edx/paragon/scss/core/core";
|
||||||
@import "~@edx/brand/paragon/overrides";
|
@import "~@edx/brand/paragon/overrides";
|
||||||
|
|
||||||
$fa-font-path: "~font-awesome/fonts";
|
$fa-font-path: "~font-awesome/fonts";
|
||||||
@@ -9,7 +9,6 @@ $fa-font-path: "~font-awesome/fonts";
|
|||||||
|
|
||||||
$input-focus-box-shadow: $input-box-shadow; // hack to get upgrade to paragon 4.0.0 to work
|
$input-focus-box-shadow: $input-box-shadow; // hack to get upgrade to paragon 4.0.0 to work
|
||||||
|
|
||||||
@import "~@edx/frontend-component-header/dist/index";
|
|
||||||
@import "~@edx/frontend-component-footer/dist/_footer";
|
@import "~@edx/frontend-component-footer/dist/_footer";
|
||||||
|
|
||||||
.text-ellipsis {
|
.text-ellipsis {
|
||||||
|
|||||||
114
src/App.test.jsx
114
src/App.test.jsx
@@ -1,23 +1,26 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { ErrorPage } from '@edx/frontend-platform/react';
|
||||||
|
|
||||||
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
|
|
||||||
|
import Footer from '@edx/frontend-component-footer';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { Alert } from '@edx/paragon';
|
||||||
|
|
||||||
import { RequestKeys } from 'data/constants/requests';
|
import { RequestKeys } from 'data/constants/requests';
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import Dashboard from 'containers/Dashboard';
|
import Dashboard from 'containers/Dashboard';
|
||||||
import LearnerDashboardHeader from 'containers/LearnerDashboardHeader';
|
import LearnerDashboardHeader from 'containers/LearnerDashboardHeader';
|
||||||
import AppWrapper from 'containers/WidgetContainers/AppWrapper';
|
|
||||||
import { App } from './App';
|
import { App } from './App';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
jest.mock('@edx/frontend-component-footer', () => ({ FooterSlot: 'FooterSlot' }));
|
jest.mock('@edx/frontend-component-footer', () => 'Footer');
|
||||||
|
|
||||||
jest.mock('containers/Dashboard', () => 'Dashboard');
|
jest.mock('containers/Dashboard', () => 'Dashboard');
|
||||||
jest.mock('containers/LearnerDashboardHeader', () => 'LearnerDashboardHeader');
|
jest.mock('containers/LearnerDashboardHeader', () => 'LearnerDashboardHeader');
|
||||||
jest.mock('containers/WidgetContainers/AppWrapper', () => 'AppWrapper');
|
jest.mock('components/ZendeskFab', () => 'ZendeskFab');
|
||||||
jest.mock('data/redux', () => ({
|
jest.mock('data/redux', () => ({
|
||||||
selectors: 'redux.selectors',
|
selectors: 'redux.selectors',
|
||||||
actions: 'redux.actions',
|
actions: 'redux.actions',
|
||||||
@@ -32,123 +35,76 @@ jest.mock('hooks', () => ({
|
|||||||
}));
|
}));
|
||||||
jest.mock('data/store', () => 'data/store');
|
jest.mock('data/store', () => 'data/store');
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform', () => ({
|
|
||||||
getConfig: jest.fn(() => ({})),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const loadData = jest.fn();
|
const loadData = jest.fn();
|
||||||
reduxHooks.useLoadData.mockReturnValue(loadData);
|
reduxHooks.useLoadData.mockReturnValue(loadData);
|
||||||
|
|
||||||
|
const logo = 'fakeLogo.png';
|
||||||
let el;
|
let el;
|
||||||
|
|
||||||
const supportEmail = 'test-support-url';
|
const supportEmail = 'test-support-url';
|
||||||
reduxHooks.usePlatformSettingsData.mockReturnValue({ supportEmail });
|
reduxHooks.usePlatformSettingsData.mockReturnValue({ supportEmail });
|
||||||
|
|
||||||
describe('App router component', () => {
|
describe('App router component', () => {
|
||||||
|
process.env.LOGO_POWERED_BY_OPEN_EDX_URL_SVG = logo;
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
describe('component', () => {
|
describe('component', () => {
|
||||||
const runBasicTests = () => {
|
const runBasicTests = () => {
|
||||||
test('snapshot', () => { expect(el.snapshot).toMatchSnapshot(); });
|
test('snapshot', () => { expect(el).toMatchSnapshot(); });
|
||||||
it('displays title in helmet component', () => {
|
it('displays title in helmet component', () => {
|
||||||
const control = el.instance
|
expect(el.find(Helmet).find('title').text()).toEqual(useIntl().formatMessage(messages.pageTitle));
|
||||||
.findByType(Helmet)[0]
|
|
||||||
.findByType('title')[0];
|
|
||||||
expect(control.children[0].el).toEqual(formatMessage(messages.pageTitle));
|
|
||||||
});
|
});
|
||||||
it('displays learner dashboard header', () => {
|
it('displays learner dashboard header', () => {
|
||||||
expect(el.instance.findByType(LearnerDashboardHeader).length).toEqual(1);
|
expect(el.find(LearnerDashboardHeader).length).toEqual(1);
|
||||||
});
|
});
|
||||||
it('wraps the header and main components in an AppWrapper widget container', () => {
|
it('wraps the page in a browser router', () => {
|
||||||
const container = el.instance.findByType(AppWrapper)[0];
|
expect(el.find(Router)).toMatchObject(el);
|
||||||
expect(container.children[0].type).toEqual('LearnerDashboardHeader');
|
});
|
||||||
expect(container.children[1].type).toEqual('main');
|
test('Footer logo drawn from env variable', () => {
|
||||||
|
expect(el.find(Footer).props().logo).toEqual(logo);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
describe('no network failure', () => {
|
describe('no network failure', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
reduxHooks.useRequestIsFailed.mockReturnValue(false);
|
reduxHooks.useRequestIsFailed.mockReturnValue(false);
|
||||||
getConfig.mockReturnValue({});
|
|
||||||
el = shallow(<App />);
|
el = shallow(<App />);
|
||||||
});
|
});
|
||||||
runBasicTests();
|
runBasicTests();
|
||||||
it('loads dashboard', () => {
|
it('loads dashboard', () => {
|
||||||
const main = el.instance.findByType('main')[0];
|
expect(el.find('main')).toMatchObject(shallow(
|
||||||
expect(main.children.length).toEqual(1);
|
<main><Dashboard /></main>,
|
||||||
const dashboard = main.children[0];
|
));
|
||||||
expect(dashboard.type).toEqual('Dashboard');
|
|
||||||
expect(
|
|
||||||
dashboard.matches(shallow(<Dashboard />)),
|
|
||||||
).toEqual(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('no network failure with optimizely url', () => {
|
|
||||||
beforeAll(() => {
|
|
||||||
reduxHooks.useRequestIsFailed.mockReturnValue(false);
|
|
||||||
getConfig.mockReturnValue({ OPTIMIZELY_URL: 'fake.url' });
|
|
||||||
el = shallow(<App />);
|
|
||||||
});
|
|
||||||
runBasicTests();
|
|
||||||
it('loads dashboard', () => {
|
|
||||||
const main = el.instance.findByType('main')[0];
|
|
||||||
expect(main.children.length).toEqual(1);
|
|
||||||
const dashboard = main.children[0];
|
|
||||||
expect(dashboard.type).toEqual('Dashboard');
|
|
||||||
expect(
|
|
||||||
dashboard.matches(shallow(<Dashboard />)),
|
|
||||||
).toEqual(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('no network failure with optimizely project id', () => {
|
|
||||||
beforeAll(() => {
|
|
||||||
reduxHooks.useRequestIsFailed.mockReturnValue(false);
|
|
||||||
getConfig.mockReturnValue({ OPTIMIZELY_PROJECT_ID: 'fakeId' });
|
|
||||||
el = shallow(<App />);
|
|
||||||
});
|
|
||||||
runBasicTests();
|
|
||||||
it('loads dashboard', () => {
|
|
||||||
const main = el.instance.findByType('main')[0];
|
|
||||||
expect(main.children.length).toEqual(1);
|
|
||||||
const dashboard = main.children[0];
|
|
||||||
expect(dashboard.type).toEqual('Dashboard');
|
|
||||||
expect(
|
|
||||||
dashboard.matches(shallow(<Dashboard />)),
|
|
||||||
).toEqual(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('initialize failure', () => {
|
describe('initialize failure', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
reduxHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.initialize);
|
reduxHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.initialize);
|
||||||
getConfig.mockReturnValue({});
|
|
||||||
el = shallow(<App />);
|
el = shallow(<App />);
|
||||||
});
|
});
|
||||||
runBasicTests();
|
runBasicTests();
|
||||||
it('loads error page', () => {
|
it('loads error page', () => {
|
||||||
const main = el.instance.findByType('main')[0];
|
expect(el.find('main')).toEqual(shallow(
|
||||||
expect(main.children.length).toEqual(1);
|
<main>
|
||||||
const alert = main.children[0];
|
<Alert variant="danger">
|
||||||
expect(alert.type).toEqual('Alert');
|
<ErrorPage message={formatMessage(messages.errorMessage, { supportEmail })} />
|
||||||
expect(alert.children.length).toEqual(1);
|
</Alert>
|
||||||
const errorPage = alert.children[0];
|
</main>,
|
||||||
expect(errorPage.type).toEqual('ErrorPage');
|
));
|
||||||
expect(errorPage.props.message).toEqual(formatMessage(messages.errorMessage, { supportEmail }));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('refresh failure', () => {
|
describe('refresh failure', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
reduxHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.refreshList);
|
reduxHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.refreshList);
|
||||||
getConfig.mockReturnValue({});
|
|
||||||
el = shallow(<App />);
|
el = shallow(<App />);
|
||||||
});
|
});
|
||||||
runBasicTests();
|
runBasicTests();
|
||||||
it('loads error page', () => {
|
it('loads error page', () => {
|
||||||
const main = el.instance.findByType('main')[0];
|
expect(el.find('main')).toEqual(shallow(
|
||||||
expect(main.children.length).toEqual(1);
|
<main>
|
||||||
const alert = main.children[0];
|
<Alert variant="danger">
|
||||||
expect(alert.type).toEqual('Alert');
|
<ErrorPage message={formatMessage(messages.errorMessage, { supportEmail })} />
|
||||||
expect(alert.children.length).toEqual(1);
|
</Alert>
|
||||||
const errorPage = alert.children[0];
|
</main>,
|
||||||
expect(errorPage.type).toEqual('ErrorPage');
|
));
|
||||||
expect(errorPage.props.message).toEqual(formatMessage(messages.errorMessage, { supportEmail }));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`App router component component initialize failure snapshot 1`] = `
|
exports[`App router component component initialize failure snapshot 1`] = `
|
||||||
<Fragment>
|
<BrowserRouter>
|
||||||
<HelmetWrapper
|
<HelmetWrapper
|
||||||
defer={true}
|
defer={true}
|
||||||
encodeSpecialCharacters={true}
|
encodeSpecialCharacters={true}
|
||||||
@@ -9,33 +9,28 @@ exports[`App router component component initialize failure snapshot 1`] = `
|
|||||||
<title>
|
<title>
|
||||||
Learner Home
|
Learner Home
|
||||||
</title>
|
</title>
|
||||||
<link
|
|
||||||
rel="shortcut icon"
|
|
||||||
type="image/x-icon"
|
|
||||||
/>
|
|
||||||
</HelmetWrapper>
|
</HelmetWrapper>
|
||||||
<div>
|
<div>
|
||||||
<AppWrapper>
|
<LearnerDashboardHeader />
|
||||||
<LearnerDashboardHeader />
|
<main>
|
||||||
<main
|
<Alert
|
||||||
id="main"
|
variant="danger"
|
||||||
>
|
>
|
||||||
<Alert
|
<ErrorPage
|
||||||
variant="danger"
|
message="If you experience repeated failures, please email support at test-support-url"
|
||||||
>
|
/>
|
||||||
<ErrorPage
|
</Alert>
|
||||||
message="If you experience repeated failures, please email support at test-support-url"
|
</main>
|
||||||
/>
|
<Footer
|
||||||
</Alert>
|
logo="fakeLogo.png"
|
||||||
</main>
|
/>
|
||||||
</AppWrapper>
|
<ZendeskFab />
|
||||||
<FooterSlot />
|
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</BrowserRouter>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`App router component component no network failure snapshot 1`] = `
|
exports[`App router component component no network failure snapshot 1`] = `
|
||||||
<Fragment>
|
<BrowserRouter>
|
||||||
<HelmetWrapper
|
<HelmetWrapper
|
||||||
defer={true}
|
defer={true}
|
||||||
encodeSpecialCharacters={true}
|
encodeSpecialCharacters={true}
|
||||||
@@ -43,83 +38,22 @@ exports[`App router component component no network failure snapshot 1`] = `
|
|||||||
<title>
|
<title>
|
||||||
Learner Home
|
Learner Home
|
||||||
</title>
|
</title>
|
||||||
<link
|
|
||||||
rel="shortcut icon"
|
|
||||||
type="image/x-icon"
|
|
||||||
/>
|
|
||||||
</HelmetWrapper>
|
</HelmetWrapper>
|
||||||
<div>
|
<div>
|
||||||
<AppWrapper>
|
<LearnerDashboardHeader />
|
||||||
<LearnerDashboardHeader />
|
<main>
|
||||||
<main
|
<Dashboard />
|
||||||
id="main"
|
</main>
|
||||||
>
|
<Footer
|
||||||
<Dashboard />
|
logo="fakeLogo.png"
|
||||||
</main>
|
|
||||||
</AppWrapper>
|
|
||||||
<FooterSlot />
|
|
||||||
</div>
|
|
||||||
</Fragment>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`App router component component no network failure with optimizely project id snapshot 1`] = `
|
|
||||||
<Fragment>
|
|
||||||
<HelmetWrapper
|
|
||||||
defer={true}
|
|
||||||
encodeSpecialCharacters={true}
|
|
||||||
>
|
|
||||||
<title>
|
|
||||||
Learner Home
|
|
||||||
</title>
|
|
||||||
<link
|
|
||||||
rel="shortcut icon"
|
|
||||||
type="image/x-icon"
|
|
||||||
/>
|
/>
|
||||||
</HelmetWrapper>
|
<ZendeskFab />
|
||||||
<div>
|
|
||||||
<AppWrapper>
|
|
||||||
<LearnerDashboardHeader />
|
|
||||||
<main
|
|
||||||
id="main"
|
|
||||||
>
|
|
||||||
<Dashboard />
|
|
||||||
</main>
|
|
||||||
</AppWrapper>
|
|
||||||
<FooterSlot />
|
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</BrowserRouter>
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`App router component component no network failure with optimizely url snapshot 1`] = `
|
|
||||||
<Fragment>
|
|
||||||
<HelmetWrapper
|
|
||||||
defer={true}
|
|
||||||
encodeSpecialCharacters={true}
|
|
||||||
>
|
|
||||||
<title>
|
|
||||||
Learner Home
|
|
||||||
</title>
|
|
||||||
<link
|
|
||||||
rel="shortcut icon"
|
|
||||||
type="image/x-icon"
|
|
||||||
/>
|
|
||||||
</HelmetWrapper>
|
|
||||||
<div>
|
|
||||||
<AppWrapper>
|
|
||||||
<LearnerDashboardHeader />
|
|
||||||
<main
|
|
||||||
id="main"
|
|
||||||
>
|
|
||||||
<Dashboard />
|
|
||||||
</main>
|
|
||||||
</AppWrapper>
|
|
||||||
<FooterSlot />
|
|
||||||
</div>
|
|
||||||
</Fragment>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`App router component component refresh failure snapshot 1`] = `
|
exports[`App router component component refresh failure snapshot 1`] = `
|
||||||
<Fragment>
|
<BrowserRouter>
|
||||||
<HelmetWrapper
|
<HelmetWrapper
|
||||||
defer={true}
|
defer={true}
|
||||||
encodeSpecialCharacters={true}
|
encodeSpecialCharacters={true}
|
||||||
@@ -127,27 +61,22 @@ exports[`App router component component refresh failure snapshot 1`] = `
|
|||||||
<title>
|
<title>
|
||||||
Learner Home
|
Learner Home
|
||||||
</title>
|
</title>
|
||||||
<link
|
|
||||||
rel="shortcut icon"
|
|
||||||
type="image/x-icon"
|
|
||||||
/>
|
|
||||||
</HelmetWrapper>
|
</HelmetWrapper>
|
||||||
<div>
|
<div>
|
||||||
<AppWrapper>
|
<LearnerDashboardHeader />
|
||||||
<LearnerDashboardHeader />
|
<main>
|
||||||
<main
|
<Alert
|
||||||
id="main"
|
variant="danger"
|
||||||
>
|
>
|
||||||
<Alert
|
<ErrorPage
|
||||||
variant="danger"
|
message="If you experience repeated failures, please email support at test-support-url"
|
||||||
>
|
/>
|
||||||
<ErrorPage
|
</Alert>
|
||||||
message="If you experience repeated failures, please email support at test-support-url"
|
</main>
|
||||||
/>
|
<Footer
|
||||||
</Alert>
|
logo="fakeLogo.png"
|
||||||
</main>
|
/>
|
||||||
</AppWrapper>
|
<ZendeskFab />
|
||||||
<FooterSlot />
|
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</BrowserRouter>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,43 +1,30 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`app registry subscribe: APP_INIT_ERROR. snapshot: displays an ErrorPage to root element 1`] = `
|
exports[`app registry subscribe: APP_INIT_ERROR. snapshot: displays an ErrorPage to root element 1`] = `
|
||||||
<UNDEFINED>
|
<ErrorPage
|
||||||
<ErrorPage
|
message="test-error-message"
|
||||||
message="test-error-message"
|
/>
|
||||||
/>
|
|
||||||
</UNDEFINED>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`app registry subscribe: APP_READY. links App to root element 1`] = `
|
exports[`app registry subscribe: APP_READY. links App to root element 1`] = `
|
||||||
<UNDEFINED>
|
<AppProvider
|
||||||
<AppProvider
|
store={
|
||||||
store={
|
Object {
|
||||||
{
|
"redux": "store",
|
||||||
"redux": "store",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
>
|
}
|
||||||
<NoticesWrapper>
|
>
|
||||||
<Routes>
|
<NoticesWrapper>
|
||||||
<Route
|
<Switch>
|
||||||
element={
|
<PageRoute
|
||||||
<PageWrap>
|
path="/"
|
||||||
<App />
|
>
|
||||||
</PageWrap>
|
<App />
|
||||||
}
|
</PageRoute>
|
||||||
path="/"
|
<Redirect
|
||||||
/>
|
to="/"
|
||||||
<Route
|
/>
|
||||||
element={
|
</Switch>
|
||||||
<Navigate
|
</NoticesWrapper>
|
||||||
replace={true}
|
</AppProvider>
|
||||||
to="/"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
path="*"
|
|
||||||
/>
|
|
||||||
</Routes>
|
|
||||||
</NoticesWrapper>
|
|
||||||
</AppProvider>
|
|
||||||
</UNDEFINED>
|
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { Alert } from '@openedx/paragon';
|
import { Alert } from '@edx/paragon';
|
||||||
import { Info } from '@openedx/paragon/icons';
|
import { Info } from '@edx/paragon/icons';
|
||||||
|
|
||||||
export const Banner = ({
|
export const Banner = ({
|
||||||
children, variant, icon, className,
|
children, variant, icon, className,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { Alert } from '@openedx/paragon';
|
import { Alert } from '@edx/paragon';
|
||||||
|
|
||||||
import Banner from './Banner';
|
import Banner from './Banner';
|
||||||
|
|
||||||
@@ -11,17 +11,17 @@ describe('Banner', () => {
|
|||||||
describe('snapshot', () => {
|
describe('snapshot', () => {
|
||||||
test('renders default banner', () => {
|
test('renders default banner', () => {
|
||||||
const wrapper = shallow(<Banner {...props} />);
|
const wrapper = shallow(<Banner {...props} />);
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
test('renders with variants', () => {
|
test('renders with variants', () => {
|
||||||
const wrapper = shallow(<Banner {...props} variant="success" />);
|
const wrapper = shallow(<Banner {...props} variant="success" />);
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
|
||||||
expect(wrapper.instance.findByType(Alert)[0].props.variant).toEqual('success');
|
expect(wrapper.find(Alert).prop('variant')).toEqual('success');
|
||||||
});
|
});
|
||||||
test('renders with custom class', () => {
|
test('renders with custom class', () => {
|
||||||
const wrapper = shallow(<Banner {...props} className="custom-class" />);
|
const wrapper = shallow(<Banner {...props} className="custom-class" />);
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
17
src/components/EmailLink.jsx
Normal file
17
src/components/EmailLink.jsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { MailtoLink } from '@edx/paragon';
|
||||||
|
|
||||||
|
export const EmailLink = ({ address }) => {
|
||||||
|
if (!address) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<MailtoLink to={address}>{address}</MailtoLink>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
EmailLink.defaultProps = { address: null };
|
||||||
|
EmailLink.propTypes = { address: PropTypes.string };
|
||||||
|
|
||||||
|
export default EmailLink;
|
||||||
16
src/components/EmailLink.test.jsx
Normal file
16
src/components/EmailLink.test.jsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
|
import EmailLink from './EmailLink';
|
||||||
|
|
||||||
|
describe('EmailLink', () => {
|
||||||
|
it('renders null when no address is provided', () => {
|
||||||
|
const wrapper = shallow(<EmailLink />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
expect(wrapper.isEmptyRender()).toEqual(true);
|
||||||
|
});
|
||||||
|
it('renders a MailtoLink when an address is provided', () => {
|
||||||
|
const wrapper = shallow(<EmailLink address="test@email.com" />);
|
||||||
|
expect(wrapper.find('MailtoLink').length).toEqual(1);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import useNoticesWrapperData from './hooks';
|
import useNoticesWrapperData from './hooks';
|
||||||
import NoticesWrapper from '.';
|
import NoticesWrapper from '.';
|
||||||
@@ -22,15 +22,13 @@ describe('NoticesWrapper component', () => {
|
|||||||
it('does not show children if redirected', () => {
|
it('does not show children if redirected', () => {
|
||||||
useNoticesWrapperData.mockReturnValueOnce({ isRedirected: true });
|
useNoticesWrapperData.mockReturnValueOnce({ isRedirected: true });
|
||||||
el = shallow(<NoticesWrapper>{children}</NoticesWrapper>);
|
el = shallow(<NoticesWrapper>{children}</NoticesWrapper>);
|
||||||
expect(el.instance.children.length).toEqual(0);
|
expect(el.children().length).toEqual(0);
|
||||||
});
|
});
|
||||||
it('shows children if not redirected', () => {
|
it('shows children if not redirected', () => {
|
||||||
el = shallow(<NoticesWrapper>{children}</NoticesWrapper>);
|
el = shallow(<NoticesWrapper>{children}</NoticesWrapper>);
|
||||||
expect(el.instance.children.length).toEqual(2);
|
expect(el.children().length).toEqual(2);
|
||||||
expect(el.instance.children[0].type).toEqual(shallow(children[0]).type);
|
expect(el.children().at(0).matchesElement(children[0])).toEqual(true);
|
||||||
expect(el.instance.props).toEqual(shallow(children[0]).props);
|
expect(el.children().at(1).matchesElement(children[1])).toEqual(true);
|
||||||
expect(el.instance.children[1].type).toEqual(shallow(children[1]).type);
|
|
||||||
expect(el.instance.props).toEqual(shallow(children[1]).props);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
65
src/components/ZendeskFab/__snapshots__/index.test.jsx.snap
Normal file
65
src/components/ZendeskFab/__snapshots__/index.test.jsx.snap
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`ZendeskFab snapshot 1`] = `
|
||||||
|
<Zendesk
|
||||||
|
cookies={true}
|
||||||
|
defer={true}
|
||||||
|
webWidget={
|
||||||
|
Object {
|
||||||
|
"answerBot": Object {
|
||||||
|
"avatar": Object {
|
||||||
|
"name": Object {
|
||||||
|
"*": "edX Support",
|
||||||
|
},
|
||||||
|
"url": "https://edx-cdn.org/v3/prod/favicon.ico",
|
||||||
|
},
|
||||||
|
"contactOnlyAfterQuery": true,
|
||||||
|
"suppress": false,
|
||||||
|
"title": Object {
|
||||||
|
"*": "edX Support",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"chat": Object {
|
||||||
|
"departments": Object {
|
||||||
|
"enabled": Array [
|
||||||
|
"account settings",
|
||||||
|
"billing and payments",
|
||||||
|
"certificates",
|
||||||
|
"deadlines",
|
||||||
|
"errors and technical issues",
|
||||||
|
"other",
|
||||||
|
"proctoring",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"suppress": false,
|
||||||
|
},
|
||||||
|
"contactForm": Object {
|
||||||
|
"attachments": true,
|
||||||
|
"selectTicketForm": Object {
|
||||||
|
"*": "Please choose your request type:",
|
||||||
|
},
|
||||||
|
"ticketForms": Array [
|
||||||
|
Object {
|
||||||
|
"fields": Array [
|
||||||
|
Object {
|
||||||
|
"id": "description",
|
||||||
|
"prefill": Object {
|
||||||
|
"*": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"id": 360003368814,
|
||||||
|
"subject": false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"contactOptions": Object {
|
||||||
|
"enabled": false,
|
||||||
|
},
|
||||||
|
"helpCenter": Object {
|
||||||
|
"originalArticleButton": true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
`;
|
||||||
56
src/components/ZendeskFab/index.jsx
Normal file
56
src/components/ZendeskFab/index.jsx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
import Zendesk from 'react-zendesk';
|
||||||
|
import messages from './messages';
|
||||||
|
|
||||||
|
const ZendeskFab = () => {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const setting = {
|
||||||
|
cookies: true,
|
||||||
|
webWidget: {
|
||||||
|
contactOptions: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
chat: {
|
||||||
|
suppress: false,
|
||||||
|
departments: {
|
||||||
|
enabled: ['account settings', 'billing and payments', 'certificates', 'deadlines', 'errors and technical issues', 'other', 'proctoring'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
contactForm: {
|
||||||
|
ticketForms: [
|
||||||
|
{
|
||||||
|
id: 360003368814,
|
||||||
|
subject: false,
|
||||||
|
fields: [{ id: 'description', prefill: { '*': '' } }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
selectTicketForm: {
|
||||||
|
'*': formatMessage(messages.selectTicketForm),
|
||||||
|
},
|
||||||
|
attachments: true,
|
||||||
|
},
|
||||||
|
helpCenter: {
|
||||||
|
originalArticleButton: true,
|
||||||
|
},
|
||||||
|
answerBot: {
|
||||||
|
suppress: false,
|
||||||
|
contactOnlyAfterQuery: true,
|
||||||
|
title: { '*': formatMessage(messages.supportTitle) },
|
||||||
|
avatar: {
|
||||||
|
url: 'https://edx-cdn.org/v3/prod/favicon.ico',
|
||||||
|
name: { '*': formatMessage(messages.supportTitle) },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Zendesk defer zendeskKey={getConfig().ZENDESK_KEY} {...setting} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ZendeskFab;
|
||||||
12
src/components/ZendeskFab/index.test.jsx
Normal file
12
src/components/ZendeskFab/index.test.jsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
|
import ZendeskFab from '.';
|
||||||
|
|
||||||
|
jest.mock('react-zendesk', () => 'Zendesk');
|
||||||
|
|
||||||
|
describe('ZendeskFab', () => {
|
||||||
|
test('snapshot', () => {
|
||||||
|
const wrapper = shallow(<ZendeskFab />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
16
src/components/ZendeskFab/messages.js
Normal file
16
src/components/ZendeskFab/messages.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { StrictDict } from 'utils';
|
||||||
|
|
||||||
|
export const messages = StrictDict({
|
||||||
|
supportTitle: {
|
||||||
|
id: 'zendesk.supportTitle',
|
||||||
|
description: 'Title for the support button',
|
||||||
|
defaultMessage: 'edX Support',
|
||||||
|
},
|
||||||
|
selectTicketForm: {
|
||||||
|
id: 'zendesk.selectTicketForm',
|
||||||
|
description: 'Select ticket form',
|
||||||
|
defaultMessage: 'Please choose your request type:',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default messages;
|
||||||
11
src/components/__snapshots__/EmailLink.test.jsx.snap
Normal file
11
src/components/__snapshots__/EmailLink.test.jsx.snap
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`EmailLink renders a MailtoLink when an address is provided 1`] = `
|
||||||
|
<MailtoLink
|
||||||
|
to="test@email.com"
|
||||||
|
>
|
||||||
|
test@email.com
|
||||||
|
</MailtoLink>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`EmailLink renders null when no address is provided 1`] = `""`;
|
||||||
@@ -12,13 +12,10 @@ const configuration = {
|
|||||||
// ACCESS_TOKEN_COOKIE_NAME: process.env.ACCESS_TOKEN_COOKIE_NAME,
|
// ACCESS_TOKEN_COOKIE_NAME: process.env.ACCESS_TOKEN_COOKIE_NAME,
|
||||||
LEARNING_BASE_URL: process.env.LEARNING_BASE_URL,
|
LEARNING_BASE_URL: process.env.LEARNING_BASE_URL,
|
||||||
SESSION_COOKIE_DOMAIN: process.env.SESSION_COOKIE_DOMAIN || '',
|
SESSION_COOKIE_DOMAIN: process.env.SESSION_COOKIE_DOMAIN || '',
|
||||||
|
ZENDESK_KEY: process.env.ZENDESK_KEY,
|
||||||
SUPPORT_URL: process.env.SUPPORT_URL || null,
|
SUPPORT_URL: process.env.SUPPORT_URL || null,
|
||||||
ENABLE_NOTICES: process.env.ENABLE_NOTICES || null,
|
ENABLE_NOTICES: process.env.ENABLE_NOTICES || null,
|
||||||
CAREER_LINK_URL: process.env.CAREER_LINK_URL || 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',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const features = {};
|
const features = {};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "@openedx/paragon/scss/core/core";
|
@import "@edx/paragon/scss/core/core";
|
||||||
|
|
||||||
.course-card {
|
.course-card {
|
||||||
.card {
|
.card {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useWindowSize, breakpoints } from '@openedx/paragon';
|
import { useWindowSize, breakpoints } from '@edx/paragon';
|
||||||
|
|
||||||
export const useIsCollapsed = () => {
|
export const useIsCollapsed = () => {
|
||||||
const { width } = useWindowSize();
|
const { width } = useWindowSize();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useWindowSize, breakpoints } from '@openedx/paragon';
|
import { useWindowSize, breakpoints } from '@edx/paragon';
|
||||||
import useIsCollapsed from './hooks';
|
import useIsCollapsed from './hooks';
|
||||||
|
|
||||||
describe('useIsCollapsed', () => {
|
describe('useIsCollapsed', () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button } from '@openedx/paragon';
|
import { Button } from '@edx/paragon';
|
||||||
|
|
||||||
import useIsCollapsed from './hooks';
|
import useIsCollapsed from './hooks';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import ActionButton from '.';
|
import ActionButton from '.';
|
||||||
|
|
||||||
@@ -14,12 +14,12 @@ describe('ActionButton', () => {
|
|||||||
test('is collapsed', () => {
|
test('is collapsed', () => {
|
||||||
useIsCollapsed.mockReturnValueOnce(true);
|
useIsCollapsed.mockReturnValueOnce(true);
|
||||||
const wrapper = shallow(<ActionButton {...props} />);
|
const wrapper = shallow(<ActionButton {...props} />);
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
test('is not collapsed', () => {
|
test('is not collapsed', () => {
|
||||||
useIsCollapsed.mockReturnValueOnce(false);
|
useIsCollapsed.mockReturnValueOnce(false);
|
||||||
const wrapper = shallow(<ActionButton {...props} />);
|
const wrapper = shallow(<ActionButton {...props} />);
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,13 +12,11 @@ import messages from './messages';
|
|||||||
export const BeginCourseButton = ({ cardId }) => {
|
export const BeginCourseButton = ({ cardId }) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const { homeUrl } = reduxHooks.useCardCourseRunData(cardId);
|
const { homeUrl } = reduxHooks.useCardCourseRunData(cardId);
|
||||||
const execEdTrackingParam = reduxHooks.useCardExecEdTrackingParam(cardId);
|
|
||||||
const { disableBeginCourse } = useActionDisabledState(cardId);
|
const { disableBeginCourse } = useActionDisabledState(cardId);
|
||||||
|
|
||||||
const handleClick = reduxHooks.useTrackCourseEvent(
|
const handleClick = reduxHooks.useTrackCourseEvent(
|
||||||
track.course.enterCourseClicked,
|
track.course.enterCourseClicked,
|
||||||
cardId,
|
cardId,
|
||||||
homeUrl + execEdTrackingParam,
|
homeUrl,
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
|
import { htmlProps } from 'data/constants/htmlKeys';
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import track from 'tracking';
|
import track from 'tracking';
|
||||||
import useActionDisabledState from '../hooks';
|
import useActionDisabledState from '../hooks';
|
||||||
@@ -13,22 +14,17 @@ jest.mock('tracking', () => ({
|
|||||||
|
|
||||||
jest.mock('hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
useCardCourseRunData: jest.fn(),
|
useCardCourseRunData: jest.fn(() => ({ homeUrl: 'home-url' })),
|
||||||
useCardExecEdTrackingParam: jest.fn(),
|
useTrackCourseEvent: jest.fn(
|
||||||
useTrackCourseEvent: jest.fn(),
|
(eventName, cardId, upgradeUrl) => ({ trackCourseEvent: { eventName, cardId, upgradeUrl } }),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
jest.mock('../hooks', () => jest.fn(() => ({ disableBeginCourse: false })));
|
jest.mock('../hooks', () => jest.fn(() => ({ disableBeginCourse: false })));
|
||||||
jest.mock('./ActionButton', () => 'ActionButton');
|
jest.mock('./ActionButton', () => 'ActionButton');
|
||||||
|
|
||||||
let wrapper;
|
let wrapper;
|
||||||
const homeUrl = 'home-url';
|
const { homeUrl } = reduxHooks.useCardCourseRunData();
|
||||||
reduxHooks.useCardCourseRunData.mockReturnValue({ homeUrl });
|
|
||||||
const execEdPath = (cardId) => `exec-ed-tracking-path=${cardId}`;
|
|
||||||
reduxHooks.useCardExecEdTrackingParam.mockImplementation(execEdPath);
|
|
||||||
reduxHooks.useTrackCourseEvent.mockImplementation(
|
|
||||||
(eventName, cardId, url) => ({ trackCourseEvent: { eventName, cardId, url } }),
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('BeginCourseButton', () => {
|
describe('BeginCourseButton', () => {
|
||||||
const props = {
|
const props = {
|
||||||
@@ -37,50 +33,27 @@ describe('BeginCourseButton', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
describe('snapshot', () => {
|
||||||
|
test('renders default button when learner has access to the course', () => {
|
||||||
|
wrapper = shallow(<BeginCourseButton {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
expect(wrapper.prop(htmlProps.disabled)).toEqual(false);
|
||||||
|
expect(wrapper.prop(htmlProps.onClick)).toEqual(reduxHooks.useTrackCourseEvent(
|
||||||
|
track.course.enterCourseClicked,
|
||||||
|
props.cardId,
|
||||||
|
homeUrl,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('behavior', () => {
|
describe('behavior', () => {
|
||||||
it('initializes course run data with cardId', () => {
|
it('initializes course run data with cardId', () => {
|
||||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
wrapper = shallow(<BeginCourseButton {...props} />);
|
||||||
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(props.cardId);
|
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(props.cardId);
|
||||||
});
|
});
|
||||||
it('loads exec education path param', () => {
|
test('disabled states', () => {
|
||||||
|
useActionDisabledState.mockReturnValueOnce({ disableBeginCourse: true });
|
||||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
wrapper = shallow(<BeginCourseButton {...props} />);
|
||||||
expect(reduxHooks.useCardExecEdTrackingParam).toHaveBeenCalledWith(props.cardId);
|
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
|
||||||
});
|
|
||||||
it('loads disabled states for begin action from action hooks', () => {
|
|
||||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
|
||||||
expect(useActionDisabledState).toHaveBeenCalledWith(props.cardId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('snapshot', () => {
|
|
||||||
describe('disabled', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
useActionDisabledState.mockReturnValueOnce({ disableBeginCourse: true });
|
|
||||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
|
||||||
});
|
|
||||||
test('snapshot', () => {
|
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
it('should be disabled', () => {
|
|
||||||
expect(wrapper.instance.props.disabled).toEqual(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('enabled', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
|
||||||
});
|
|
||||||
test('snapshot', () => {
|
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
it('should be enabled', () => {
|
|
||||||
expect(wrapper.instance.props.disabled).toEqual(false);
|
|
||||||
});
|
|
||||||
it('should track enter course clicked event on click, with exec ed param', () => {
|
|
||||||
expect(wrapper.instance.props.onClick).toEqual(reduxHooks.useTrackCourseEvent(
|
|
||||||
track.course.enterCourseClicked,
|
|
||||||
props.cardId,
|
|
||||||
homeUrl + execEdPath(props.cardId),
|
|
||||||
));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,13 +12,11 @@ import messages from './messages';
|
|||||||
export const ResumeButton = ({ cardId }) => {
|
export const ResumeButton = ({ cardId }) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const { resumeUrl } = reduxHooks.useCardCourseRunData(cardId);
|
const { resumeUrl } = reduxHooks.useCardCourseRunData(cardId);
|
||||||
const execEdTrackingParam = reduxHooks.useCardExecEdTrackingParam(cardId);
|
|
||||||
const { disableResumeCourse } = useActionDisabledState(cardId);
|
const { disableResumeCourse } = useActionDisabledState(cardId);
|
||||||
|
|
||||||
const handleClick = reduxHooks.useTrackCourseEvent(
|
const handleClick = reduxHooks.useTrackCourseEvent(
|
||||||
track.course.enterCourseClicked,
|
track.course.enterCourseClicked,
|
||||||
cardId,
|
cardId,
|
||||||
resumeUrl + execEdTrackingParam,
|
resumeUrl,
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
|||||||
@@ -1,84 +1,57 @@
|
|||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
|
import { htmlProps } from 'data/constants/htmlKeys';
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import track from 'tracking';
|
import track from 'tracking';
|
||||||
import useActionDisabledState from '../hooks';
|
import useActionDisabledState from '../hooks';
|
||||||
import ResumeButton from './ResumeButton';
|
import ResumeButton from './ResumeButton';
|
||||||
|
|
||||||
jest.mock('tracking', () => ({
|
|
||||||
course: {
|
|
||||||
enterCourseClicked: jest.fn().mockName('segment.enterCourseClicked'),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
useCardCourseRunData: jest.fn(),
|
useCardCourseRunData: jest.fn(() => ({ resumeUrl: 'resumeUrl' })),
|
||||||
useCardExecEdTrackingParam: jest.fn(),
|
useTrackCourseEvent: (eventName, cardId, url) => jest
|
||||||
useTrackCourseEvent: jest.fn(),
|
.fn()
|
||||||
|
.mockName(`useTrackCourseEvent('${eventName}', '${cardId}', '${url}')`),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
jest.mock('../hooks', () => jest.fn(() => ({ disableResumeCourse: false })));
|
jest.mock('../hooks', () => jest.fn(() => ({ disableResumeCourse: false })));
|
||||||
|
jest.mock('tracking', () => ({
|
||||||
|
course: {
|
||||||
|
enterCourseClicked: 'enterCourseClicked',
|
||||||
|
},
|
||||||
|
}));
|
||||||
jest.mock('./ActionButton', () => 'ActionButton');
|
jest.mock('./ActionButton', () => 'ActionButton');
|
||||||
|
|
||||||
const resumeUrl = 'resume-url';
|
const { resumeUrl } = reduxHooks.useCardCourseRunData();
|
||||||
reduxHooks.useCardCourseRunData.mockReturnValue({ resumeUrl });
|
|
||||||
const execEdPath = (cardId) => `exec-ed-tracking-path=${cardId}`;
|
|
||||||
reduxHooks.useCardExecEdTrackingParam.mockImplementation(execEdPath);
|
|
||||||
reduxHooks.useTrackCourseEvent.mockImplementation(
|
|
||||||
(eventName, cardId, url) => ({ trackCourseEvent: { eventName, cardId, url } }),
|
|
||||||
);
|
|
||||||
|
|
||||||
let wrapper;
|
|
||||||
|
|
||||||
describe('ResumeButton', () => {
|
describe('ResumeButton', () => {
|
||||||
const props = {
|
const props = {
|
||||||
cardId: 'cardId',
|
cardId: 'cardId',
|
||||||
};
|
};
|
||||||
describe('behavior', () => {
|
describe('snapshot', () => {
|
||||||
it('initializes course run data with cardId', () => {
|
test('renders default button when learner has access to the course', () => {
|
||||||
wrapper = shallow(<ResumeButton {...props} />);
|
const wrapper = shallow(<ResumeButton {...props} />);
|
||||||
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(props.cardId);
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
expect(wrapper.prop(htmlProps.disabled)).toEqual(false);
|
||||||
it('loads exec education path param', () => {
|
expect(wrapper.prop(htmlProps.onClick).getMockName()).toContain(
|
||||||
wrapper = shallow(<ResumeButton {...props} />);
|
'useTrackCourseEvent',
|
||||||
expect(reduxHooks.useCardExecEdTrackingParam).toHaveBeenCalledWith(props.cardId);
|
track.course.enterCourseClicked,
|
||||||
});
|
props.cardId,
|
||||||
it('loads disabled states for resume action from action hooks', () => {
|
resumeUrl,
|
||||||
wrapper = shallow(<ResumeButton {...props} />);
|
);
|
||||||
expect(useActionDisabledState).toHaveBeenCalledWith(props.cardId);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('snapshot', () => {
|
describe('behavior', () => {
|
||||||
describe('disabled', () => {
|
it('initializes course run data based on cardId', () => {
|
||||||
beforeEach(() => {
|
shallow(<ResumeButton {...props} />);
|
||||||
useActionDisabledState.mockReturnValueOnce({ disableResumeCourse: true });
|
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(
|
||||||
wrapper = shallow(<ResumeButton {...props} />);
|
props.cardId,
|
||||||
});
|
);
|
||||||
test('snapshot', () => {
|
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
it('should be disabled', () => {
|
|
||||||
expect(wrapper.instance.props.disabled).toEqual(true);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
describe('enabled', () => {
|
test('disabled states', () => {
|
||||||
beforeEach(() => {
|
useActionDisabledState.mockReturnValueOnce({ disableResumeCourse: true });
|
||||||
wrapper = shallow(<ResumeButton {...props} />);
|
const wrapper = shallow(<ResumeButton {...props} />);
|
||||||
});
|
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
|
||||||
test('snapshot', () => {
|
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
it('should be enabled', () => {
|
|
||||||
expect(wrapper.instance.props.disabled).toEqual(false);
|
|
||||||
});
|
|
||||||
it('should track enter course clicked event on click, with exec ed param', () => {
|
|
||||||
expect(wrapper.instance.props.onClick).toEqual(reduxHooks.useTrackCourseEvent(
|
|
||||||
track.course.enterCourseClicked,
|
|
||||||
props.cardId,
|
|
||||||
resumeUrl + execEdPath(props.cardId),
|
|
||||||
));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
|
import { htmlProps } from 'data/constants/htmlKeys';
|
||||||
import useActionDisabledState from '../hooks';
|
import useActionDisabledState from '../hooks';
|
||||||
|
|
||||||
import SelectSessionButton from './SelectSessionButton';
|
import SelectSessionButton from './SelectSessionButton';
|
||||||
@@ -18,17 +19,17 @@ let wrapper;
|
|||||||
describe('SelectSessionButton', () => {
|
describe('SelectSessionButton', () => {
|
||||||
const props = { cardId: 'cardId' };
|
const props = { cardId: 'cardId' };
|
||||||
it('default render', () => {
|
it('default render', () => {
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
wrapper = shallow(<SelectSessionButton {...props} />);
|
wrapper = shallow(<SelectSessionButton {...props} />);
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper.prop(htmlProps.disabled)).toEqual(false);
|
||||||
expect(wrapper.instance.props.disabled).toEqual(false);
|
expect(wrapper.prop(htmlProps.onClick).getMockName()).toEqual(
|
||||||
expect(wrapper.instance.props.onClick.getMockName()).toEqual(
|
|
||||||
reduxHooks.useUpdateSelectSessionModalCallback().getMockName(),
|
reduxHooks.useUpdateSelectSessionModalCallback().getMockName(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test('disabled states', () => {
|
test('disabled states', () => {
|
||||||
useActionDisabledState.mockReturnValueOnce({ disableSelectSession: true });
|
useActionDisabledState.mockReturnValueOnce({ disableSelectSession: true });
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
wrapper = shallow(<SelectSessionButton {...props} />);
|
wrapper = shallow(<SelectSessionButton {...props} />);
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
|
||||||
expect(wrapper.instance.props.disabled).toEqual(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { Locked } from '@edx/paragon/icons';
|
||||||
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
import track from 'tracking';
|
||||||
|
import { reduxHooks } from 'hooks';
|
||||||
|
import useActionDisabledState from '../hooks';
|
||||||
|
|
||||||
|
import ActionButton from './ActionButton';
|
||||||
|
import messages from './messages';
|
||||||
|
|
||||||
|
export const UpgradeButton = ({ cardId }) => {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
const { upgradeUrl } = reduxHooks.useCardCourseRunData(cardId);
|
||||||
|
const { disableUpgradeCourse } = useActionDisabledState(cardId);
|
||||||
|
|
||||||
|
const trackUpgradeClick = reduxHooks.useTrackCourseEvent(
|
||||||
|
track.course.upgradeClicked,
|
||||||
|
cardId,
|
||||||
|
upgradeUrl,
|
||||||
|
);
|
||||||
|
|
||||||
|
const enabledProps = {
|
||||||
|
as: 'a',
|
||||||
|
href: upgradeUrl,
|
||||||
|
onClick: trackUpgradeClick,
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<ActionButton
|
||||||
|
iconBefore={Locked}
|
||||||
|
variant="outline-primary"
|
||||||
|
disabled={disableUpgradeCourse}
|
||||||
|
{...!disableUpgradeCourse && enabledProps}
|
||||||
|
>
|
||||||
|
{formatMessage(messages.upgrade)}
|
||||||
|
</ActionButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
UpgradeButton.propTypes = {
|
||||||
|
cardId: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
export default UpgradeButton;
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
|
import track from 'tracking';
|
||||||
|
import { reduxHooks } from 'hooks';
|
||||||
|
import { htmlProps } from 'data/constants/htmlKeys';
|
||||||
|
import useActionDisabledState from '../hooks';
|
||||||
|
import UpgradeButton from './UpgradeButton';
|
||||||
|
|
||||||
|
jest.mock('tracking', () => ({
|
||||||
|
course: {
|
||||||
|
upgradeClicked: jest.fn().mockName('segment.trackUpgradeClicked'),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('hooks', () => ({
|
||||||
|
reduxHooks: {
|
||||||
|
useCardCourseRunData: jest.fn(),
|
||||||
|
useTrackCourseEvent: jest.fn(
|
||||||
|
(eventName, cardId, upgradeUrl) => ({ trackCourseEvent: { eventName, cardId, upgradeUrl } }),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
jest.mock('../hooks', () => jest.fn(() => ({ disableUpgradeCourse: false })));
|
||||||
|
jest.mock('./ActionButton', () => 'ActionButton');
|
||||||
|
|
||||||
|
describe('UpgradeButton', () => {
|
||||||
|
const props = {
|
||||||
|
cardId: 'cardId',
|
||||||
|
};
|
||||||
|
const upgradeUrl = 'upgradeUrl';
|
||||||
|
reduxHooks.useCardCourseRunData.mockReturnValue({ upgradeUrl });
|
||||||
|
describe('snapshot', () => {
|
||||||
|
test('can upgrade', () => {
|
||||||
|
const wrapper = shallow(<UpgradeButton {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
expect(wrapper.prop(htmlProps.disabled)).toEqual(false);
|
||||||
|
expect(wrapper.prop(htmlProps.onClick)).toEqual(reduxHooks.useTrackCourseEvent(
|
||||||
|
track.course.upgradeClicked,
|
||||||
|
props.cardId,
|
||||||
|
upgradeUrl,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
test('cannot upgrade', () => {
|
||||||
|
useActionDisabledState.mockReturnValueOnce({ disableUpgradeCourse: true });
|
||||||
|
const wrapper = shallow(<UpgradeButton {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import track from 'tracking';
|
import track from 'tracking';
|
||||||
|
import { htmlProps } from 'data/constants/htmlKeys';
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import useActionDisabledState from '../hooks';
|
import useActionDisabledState from '../hooks';
|
||||||
import ViewCourseButton from './ViewCourseButton';
|
import ViewCourseButton from './ViewCourseButton';
|
||||||
@@ -15,7 +16,7 @@ jest.mock('hooks', () => ({
|
|||||||
reduxHooks: {
|
reduxHooks: {
|
||||||
useCardCourseRunData: jest.fn(() => ({ homeUrl: 'homeUrl' })),
|
useCardCourseRunData: jest.fn(() => ({ homeUrl: 'homeUrl' })),
|
||||||
useTrackCourseEvent: jest.fn(
|
useTrackCourseEvent: jest.fn(
|
||||||
(eventName, cardId, url) => ({ trackCourseEvent: { eventName, cardId, url } }),
|
(eventName, cardId, upgradeUrl) => ({ trackCourseEvent: { eventName, cardId, upgradeUrl } }),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@@ -28,18 +29,18 @@ const homeUrl = 'homeUrl';
|
|||||||
describe('ViewCourseButton', () => {
|
describe('ViewCourseButton', () => {
|
||||||
test('learner can view course', () => {
|
test('learner can view course', () => {
|
||||||
const wrapper = shallow(<ViewCourseButton {...defaultProps} />);
|
const wrapper = shallow(<ViewCourseButton {...defaultProps} />);
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
expect(wrapper.instance.props.onClick).toEqual(reduxHooks.useTrackCourseEvent(
|
expect(wrapper.prop(htmlProps.onClick)).toEqual(reduxHooks.useTrackCourseEvent(
|
||||||
track.course.enterCourseClicked,
|
track.course.enterCourseClicked,
|
||||||
defaultProps.cardId,
|
defaultProps.cardId,
|
||||||
homeUrl,
|
homeUrl,
|
||||||
));
|
));
|
||||||
expect(wrapper.instance.props.disabled).toEqual(false);
|
expect(wrapper.prop(htmlProps.disabled)).toEqual(false);
|
||||||
});
|
});
|
||||||
test('learner cannot view course', () => {
|
test('learner cannot view course', () => {
|
||||||
useActionDisabledState.mockReturnValueOnce({ disableViewCourse: true });
|
useActionDisabledState.mockReturnValueOnce({ disableViewCourse: true });
|
||||||
const wrapper = shallow(<ViewCourseButton {...defaultProps} />);
|
const wrapper = shallow(<ViewCourseButton {...defaultProps} />);
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
expect(wrapper.instance.props.disabled).toEqual(true);
|
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,35 +1,16 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`BeginCourseButton snapshot disabled snapshot 1`] = `
|
exports[`BeginCourseButton snapshot renders default button when learner has access to the course 1`] = `
|
||||||
<ActionButton
|
|
||||||
as="a"
|
|
||||||
disabled={true}
|
|
||||||
href="#"
|
|
||||||
onClick={
|
|
||||||
{
|
|
||||||
"trackCourseEvent": {
|
|
||||||
"cardId": "cardId",
|
|
||||||
"eventName": [MockFunction segment.enterCourseClicked],
|
|
||||||
"url": "home-urlexec-ed-tracking-path=cardId",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Begin Course
|
|
||||||
</ActionButton>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`BeginCourseButton snapshot enabled snapshot 1`] = `
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
as="a"
|
as="a"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
href="#"
|
href="#"
|
||||||
onClick={
|
onClick={
|
||||||
{
|
Object {
|
||||||
"trackCourseEvent": {
|
"trackCourseEvent": Object {
|
||||||
"cardId": "cardId",
|
"cardId": "cardId",
|
||||||
"eventName": [MockFunction segment.enterCourseClicked],
|
"eventName": [MockFunction segment.enterCourseClicked],
|
||||||
"url": "home-urlexec-ed-tracking-path=cardId",
|
"upgradeUrl": "home-url",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,11 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`ResumeButton snapshot disabled snapshot 1`] = `
|
exports[`ResumeButton snapshot renders default button when learner has access to the course 1`] = `
|
||||||
<ActionButton
|
|
||||||
as="a"
|
|
||||||
disabled={true}
|
|
||||||
href="#"
|
|
||||||
onClick={
|
|
||||||
{
|
|
||||||
"trackCourseEvent": {
|
|
||||||
"cardId": "cardId",
|
|
||||||
"eventName": [MockFunction segment.enterCourseClicked],
|
|
||||||
"url": "resume-urlexec-ed-tracking-path=cardId",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Resume
|
|
||||||
</ActionButton>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`ResumeButton snapshot enabled snapshot 1`] = `
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
as="a"
|
as="a"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
href="#"
|
href="#"
|
||||||
onClick={
|
onClick={[MockFunction useTrackCourseEvent('enterCourseClicked', 'cardId', 'resumeUrl')]}
|
||||||
{
|
|
||||||
"trackCourseEvent": {
|
|
||||||
"cardId": "cardId",
|
|
||||||
"eventName": [MockFunction segment.enterCourseClicked],
|
|
||||||
"url": "resume-urlexec-ed-tracking-path=cardId",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
Resume
|
Resume
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`SelectSessionButton default render 1`] = `
|
exports[`SelectSessionButton default render 1`] = `undefined`;
|
||||||
|
|
||||||
|
exports[`SelectSessionButton disabled states 1`] = `
|
||||||
<ActionButton
|
<ActionButton
|
||||||
disabled={false}
|
disabled={false}
|
||||||
onClick={[MockFunction mockOpenSessionModal]}
|
onClick={[MockFunction mockOpenSessionModal]}
|
||||||
@@ -8,12 +10,3 @@ exports[`SelectSessionButton default render 1`] = `
|
|||||||
Select Session
|
Select Session
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SelectSessionButton disabled states 1`] = `
|
|
||||||
<ActionButton
|
|
||||||
disabled={true}
|
|
||||||
onClick={[MockFunction mockOpenSessionModal]}
|
|
||||||
>
|
|
||||||
Select Session
|
|
||||||
</ActionButton>
|
|
||||||
`;
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`UpgradeButton snapshot can upgrade 1`] = `
|
||||||
|
<ActionButton
|
||||||
|
as="a"
|
||||||
|
disabled={false}
|
||||||
|
href="upgradeUrl"
|
||||||
|
iconBefore={[MockFunction icons.Locked]}
|
||||||
|
onClick={
|
||||||
|
Object {
|
||||||
|
"trackCourseEvent": Object {
|
||||||
|
"cardId": "cardId",
|
||||||
|
"eventName": [MockFunction segment.trackUpgradeClicked],
|
||||||
|
"upgradeUrl": "upgradeUrl",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
variant="outline-primary"
|
||||||
|
>
|
||||||
|
Upgrade
|
||||||
|
</ActionButton>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`UpgradeButton snapshot cannot upgrade 1`] = `
|
||||||
|
<ActionButton
|
||||||
|
disabled={true}
|
||||||
|
iconBefore={[MockFunction icons.Locked]}
|
||||||
|
variant="outline-primary"
|
||||||
|
>
|
||||||
|
Upgrade
|
||||||
|
</ActionButton>
|
||||||
|
`;
|
||||||
@@ -6,11 +6,11 @@ exports[`ViewCourseButton learner can view course 1`] = `
|
|||||||
disabled={false}
|
disabled={false}
|
||||||
href="#"
|
href="#"
|
||||||
onClick={
|
onClick={
|
||||||
{
|
Object {
|
||||||
"trackCourseEvent": {
|
"trackCourseEvent": Object {
|
||||||
"cardId": "cardId",
|
"cardId": "cardId",
|
||||||
"eventName": [MockFunction segment.enterCourseClicked],
|
"eventName": [MockFunction segment.enterCourseClicked],
|
||||||
"url": "homeUrl",
|
"upgradeUrl": "homeUrl",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,11 +25,11 @@ exports[`ViewCourseButton learner cannot view course 1`] = `
|
|||||||
disabled={true}
|
disabled={true}
|
||||||
href="#"
|
href="#"
|
||||||
onClick={
|
onClick={
|
||||||
{
|
Object {
|
||||||
"trackCourseEvent": {
|
"trackCourseEvent": Object {
|
||||||
"cardId": "cardId",
|
"cardId": "cardId",
|
||||||
"eventName": [MockFunction segment.enterCourseClicked],
|
"eventName": [MockFunction segment.enterCourseClicked],
|
||||||
"url": "homeUrl",
|
"upgradeUrl": "homeUrl",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`CourseCardActions snapshot show begin course button when verified and not entitlement and has started 1`] = `
|
||||||
|
<ActionRow
|
||||||
|
data-test-id="CourseCardActions"
|
||||||
|
>
|
||||||
|
<BeginCourseButton
|
||||||
|
cardId="cardId"
|
||||||
|
/>
|
||||||
|
</ActionRow>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`CourseCardActions snapshot show resume button when verified and not entitlement and has started 1`] = `
|
||||||
|
<ActionRow
|
||||||
|
data-test-id="CourseCardActions"
|
||||||
|
>
|
||||||
|
<ResumeButton
|
||||||
|
cardId="cardId"
|
||||||
|
/>
|
||||||
|
</ActionRow>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`CourseCardActions snapshot show select session button when not verified and entitlement 1`] = `
|
||||||
|
<ActionRow
|
||||||
|
data-test-id="CourseCardActions"
|
||||||
|
>
|
||||||
|
<SelectSessionButton
|
||||||
|
cardId="cardId"
|
||||||
|
/>
|
||||||
|
</ActionRow>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`CourseCardActions snapshot show upgrade button when not verified and not entitlement 1`] = `
|
||||||
|
<ActionRow
|
||||||
|
data-test-id="CourseCardActions"
|
||||||
|
>
|
||||||
|
<UpgradeButton
|
||||||
|
cardId="cardId"
|
||||||
|
/>
|
||||||
|
<BeginCourseButton
|
||||||
|
cardId="cardId"
|
||||||
|
/>
|
||||||
|
</ActionRow>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`CourseCardActions snapshot show view course button when not verified and entitlement and fulfilled 1`] = `
|
||||||
|
<ActionRow
|
||||||
|
data-test-id="CourseCardActions"
|
||||||
|
>
|
||||||
|
<ViewCourseButton
|
||||||
|
cardId="cardId"
|
||||||
|
/>
|
||||||
|
</ActionRow>
|
||||||
|
`;
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { ActionRow } from '@openedx/paragon';
|
import { ActionRow } from '@edx/paragon';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
|
|
||||||
import CourseCardActionSlot from 'plugin-slots/CourseCardActionSlot';
|
import UpgradeButton from './UpgradeButton';
|
||||||
import SelectSessionButton from './SelectSessionButton';
|
import SelectSessionButton from './SelectSessionButton';
|
||||||
import BeginCourseButton from './BeginCourseButton';
|
import BeginCourseButton from './BeginCourseButton';
|
||||||
import ResumeButton from './ResumeButton';
|
import ResumeButton from './ResumeButton';
|
||||||
@@ -13,25 +13,21 @@ import ViewCourseButton from './ViewCourseButton';
|
|||||||
|
|
||||||
export const CourseCardActions = ({ cardId }) => {
|
export const CourseCardActions = ({ cardId }) => {
|
||||||
const { isEntitlement, isFulfilled } = reduxHooks.useCardEntitlementData(cardId);
|
const { isEntitlement, isFulfilled } = reduxHooks.useCardEntitlementData(cardId);
|
||||||
const {
|
const { isVerified, hasStarted } = reduxHooks.useCardEnrollmentData(cardId);
|
||||||
hasStarted,
|
|
||||||
} = reduxHooks.useCardEnrollmentData(cardId);
|
|
||||||
const { isArchived } = reduxHooks.useCardCourseRunData(cardId);
|
const { isArchived } = reduxHooks.useCardCourseRunData(cardId);
|
||||||
|
let PrimaryButton;
|
||||||
|
if (isEntitlement) {
|
||||||
|
PrimaryButton = isFulfilled ? ViewCourseButton : SelectSessionButton;
|
||||||
|
} else if (isArchived) {
|
||||||
|
PrimaryButton = ViewCourseButton;
|
||||||
|
} else {
|
||||||
|
PrimaryButton = hasStarted ? ResumeButton : BeginCourseButton;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionRow data-test-id="CourseCardActions">
|
<ActionRow data-test-id="CourseCardActions">
|
||||||
<CourseCardActionSlot cardId={cardId} />
|
{!(isEntitlement || isVerified) && <UpgradeButton cardId={cardId} />}
|
||||||
{isEntitlement && (isFulfilled
|
<PrimaryButton cardId={cardId} />
|
||||||
? <ViewCourseButton cardId={cardId} />
|
|
||||||
: <SelectSessionButton cardId={cardId} />
|
|
||||||
)}
|
|
||||||
{(isArchived && !isEntitlement) && (
|
|
||||||
<ViewCourseButton cardId={cardId} />
|
|
||||||
)}
|
|
||||||
{!(isArchived || isEntitlement) && (hasStarted
|
|
||||||
? <ResumeButton cardId={cardId} />
|
|
||||||
: <BeginCourseButton cardId={cardId} />
|
|
||||||
)}
|
|
||||||
</ActionRow>
|
</ActionRow>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
|
|
||||||
import CourseCardActionSlot from 'plugin-slots/CourseCardActionSlot';
|
|
||||||
import SelectSessionButton from './SelectSessionButton';
|
|
||||||
import BeginCourseButton from './BeginCourseButton';
|
|
||||||
import ResumeButton from './ResumeButton';
|
|
||||||
import ViewCourseButton from './ViewCourseButton';
|
|
||||||
|
|
||||||
import CourseCardActions from '.';
|
import CourseCardActions from '.';
|
||||||
|
|
||||||
jest.mock('hooks', () => ({
|
jest.mock('hooks', () => ({
|
||||||
@@ -15,83 +9,96 @@ jest.mock('hooks', () => ({
|
|||||||
useCardCourseRunData: jest.fn(),
|
useCardCourseRunData: jest.fn(),
|
||||||
useCardEnrollmentData: jest.fn(),
|
useCardEnrollmentData: jest.fn(),
|
||||||
useCardEntitlementData: jest.fn(),
|
useCardEntitlementData: jest.fn(),
|
||||||
useMasqueradeData: jest.fn(),
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('plugin-slots/CourseCardActionSlot', () => 'CustomActionButton');
|
jest.mock('./UpgradeButton', () => 'UpgradeButton');
|
||||||
jest.mock('./SelectSessionButton', () => 'SelectSessionButton');
|
jest.mock('./SelectSessionButton', () => 'SelectSessionButton');
|
||||||
jest.mock('./ViewCourseButton', () => 'ViewCourseButton');
|
jest.mock('./ViewCourseButton', () => 'ViewCourseButton');
|
||||||
jest.mock('./BeginCourseButton', () => 'BeginCourseButton');
|
jest.mock('./BeginCourseButton', () => 'BeginCourseButton');
|
||||||
jest.mock('./ResumeButton', () => 'ResumeButton');
|
jest.mock('./ResumeButton', () => 'ResumeButton');
|
||||||
|
|
||||||
const cardId = 'test-card-id';
|
|
||||||
const props = { cardId };
|
|
||||||
|
|
||||||
let el;
|
|
||||||
describe('CourseCardActions', () => {
|
describe('CourseCardActions', () => {
|
||||||
const mockHooks = ({
|
const props = {
|
||||||
isEntitlement = false,
|
cardId: 'cardId',
|
||||||
isExecEd2UCourse = false,
|
};
|
||||||
isFulfilled = false,
|
const createWrapper = ({
|
||||||
isArchived = false,
|
isEntitlement, isFulfilled, isArchived, isVerified, hasStarted,
|
||||||
isVerified = false,
|
}) => {
|
||||||
hasStarted = false,
|
|
||||||
isMasquerading = false,
|
|
||||||
} = {}) => {
|
|
||||||
reduxHooks.useCardEntitlementData.mockReturnValueOnce({ isEntitlement, isFulfilled });
|
reduxHooks.useCardEntitlementData.mockReturnValueOnce({ isEntitlement, isFulfilled });
|
||||||
reduxHooks.useCardCourseRunData.mockReturnValueOnce({ isArchived });
|
reduxHooks.useCardCourseRunData.mockReturnValueOnce({ isArchived });
|
||||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ isExecEd2UCourse, isVerified, hasStarted });
|
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ isVerified, hasStarted });
|
||||||
reduxHooks.useMasqueradeData.mockReturnValueOnce({ isMasquerading });
|
return shallow(<CourseCardActions {...props} />);
|
||||||
};
|
};
|
||||||
const render = () => {
|
describe('snapshot', () => {
|
||||||
el = shallow(<CourseCardActions {...props} />);
|
test('show upgrade button when not verified and not entitlement', () => {
|
||||||
};
|
const wrapper = createWrapper({
|
||||||
describe('behavior', () => {
|
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: false, hasStarted: false,
|
||||||
it('initializes redux hooks', () => {
|
});
|
||||||
mockHooks();
|
expect(wrapper).toMatchSnapshot();
|
||||||
render();
|
});
|
||||||
expect(reduxHooks.useCardEntitlementData).toHaveBeenCalledWith(cardId);
|
test('show select session button when not verified and entitlement', () => {
|
||||||
expect(reduxHooks.useCardEnrollmentData).toHaveBeenCalledWith(cardId);
|
const wrapper = createWrapper({
|
||||||
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(cardId);
|
isEntitlement: true, isFulfilled: false, isArchived: false, isVerified: false, hasStarted: false,
|
||||||
|
});
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
test('show begin course button when verified and not entitlement and has started', () => {
|
||||||
|
const wrapper = createWrapper({
|
||||||
|
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: true, hasStarted: false,
|
||||||
|
});
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
test('show resume button when verified and not entitlement and has started', () => {
|
||||||
|
const wrapper = createWrapper({
|
||||||
|
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: true, hasStarted: true,
|
||||||
|
});
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
test('show view course button when not verified and entitlement and fulfilled', () => {
|
||||||
|
const wrapper = createWrapper({
|
||||||
|
isEntitlement: true, isFulfilled: true, isArchived: false, isVerified: false, hasStarted: false,
|
||||||
|
});
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('output', () => {
|
|
||||||
describe('entitlement course', () => {
|
describe('behavior', () => {
|
||||||
it('renders ViewCourseButton if fulfilled', () => {
|
it('show upgrade button when not verified and not entitlement', () => {
|
||||||
mockHooks({ isEntitlement: true, isFulfilled: true });
|
const wrapper = createWrapper({
|
||||||
render();
|
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: false, hasStarted: false,
|
||||||
expect(el.instance.findByType(ViewCourseButton)[0].props.cardId).toEqual(cardId);
|
|
||||||
});
|
|
||||||
it('renders SelectSessionButton if not fulfilled', () => {
|
|
||||||
mockHooks({ isEntitlement: true });
|
|
||||||
render();
|
|
||||||
expect(el.instance.findByType(SelectSessionButton)[0].props.cardId).toEqual(cardId);
|
|
||||||
});
|
});
|
||||||
|
expect(wrapper.find('UpgradeButton')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
describe('not entitlement, verified, or exec ed', () => {
|
it('show select session button when not verified and entitlement', () => {
|
||||||
it('renders CourseCardActionSlot and ViewCourseButton for archived courses', () => {
|
const wrapper = createWrapper({
|
||||||
mockHooks({ isArchived: true });
|
isEntitlement: true, isFulfilled: false, isArchived: false, isVerified: false, hasStarted: false,
|
||||||
render();
|
|
||||||
expect(el.instance.findByType(CourseCardActionSlot)[0].props.cardId).toEqual(cardId);
|
|
||||||
expect(el.instance.findByType(ViewCourseButton)[0].props.cardId).toEqual(cardId);
|
|
||||||
});
|
});
|
||||||
describe('unstarted courses', () => {
|
expect(wrapper.find('SelectSessionButton')).toHaveLength(1);
|
||||||
it('renders CourseCardActionSlot and BeginCourseButton', () => {
|
});
|
||||||
mockHooks();
|
it('show begin course button when verified and not entitlement and has started', () => {
|
||||||
render();
|
const wrapper = createWrapper({
|
||||||
expect(el.instance.findByType(CourseCardActionSlot)[0].props.cardId).toEqual(cardId);
|
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: true, hasStarted: false,
|
||||||
expect(el.instance.findByType(BeginCourseButton)[0].props.cardId).toEqual(cardId);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
describe('active courses (started, and not archived)', () => {
|
expect(wrapper.find('BeginCourseButton')).toHaveLength(1);
|
||||||
it('renders CourseCardActionSlot and ResumeButton', () => {
|
});
|
||||||
mockHooks({ hasStarted: true });
|
it('show resume button when verified and not entitlement and has started', () => {
|
||||||
render();
|
const wrapper = createWrapper({
|
||||||
expect(el.instance.findByType(CourseCardActionSlot)[0].props.cardId).toEqual(cardId);
|
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: true, hasStarted: true,
|
||||||
expect(el.instance.findByType(ResumeButton)[0].props.cardId).toEqual(cardId);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
expect(wrapper.find('ResumeButton')).toHaveLength(1);
|
||||||
|
});
|
||||||
|
it('show view course button when not verified and entitlement and fulfilled', () => {
|
||||||
|
const wrapper = createWrapper({
|
||||||
|
isEntitlement: true, isFulfilled: true, isArchived: false, isVerified: false, hasStarted: false,
|
||||||
|
});
|
||||||
|
expect(wrapper.find('ViewCourseButton')).toHaveLength(1);
|
||||||
|
});
|
||||||
|
it('show view course button when not verified and entitlement and fulfilled and archived', () => {
|
||||||
|
const wrapper = createWrapper({
|
||||||
|
isEntitlement: true, isFulfilled: true, isArchived: true, isVerified: false, hasStarted: false,
|
||||||
|
});
|
||||||
|
expect(wrapper.find('ViewCourseButton')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { StrictDict } from 'utils';
|
||||||
|
|
||||||
const messages = defineMessages({
|
export const messages = StrictDict({
|
||||||
|
upgrade: {
|
||||||
|
id: 'learner-dash.courseCard.actions.upgrade',
|
||||||
|
description: 'Course card upgrade button text',
|
||||||
|
defaultMessage: 'Upgrade',
|
||||||
|
},
|
||||||
beginCourse: {
|
beginCourse: {
|
||||||
id: 'learner-dash.courseCard.actions.beginCourse',
|
id: 'learner-dash.courseCard.actions.beginCourse',
|
||||||
description: 'Course card begin-course button text',
|
description: 'Course card begin-course button text',
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { MailtoLink, Hyperlink } from '@openedx/paragon';
|
import { MailtoLink, Hyperlink } from '@edx/paragon';
|
||||||
import { CheckCircle } from '@openedx/paragon/icons';
|
import { CheckCircle } from '@edx/paragon/icons';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { utilHooks, reduxHooks } from 'hooks';
|
import { utilHooks, reduxHooks } from 'hooks';
|
||||||
@@ -26,14 +26,17 @@ export const CertificateBanner = ({ cardId }) => {
|
|||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const formatDate = useFormatDate();
|
const formatDate = useFormatDate();
|
||||||
|
|
||||||
const emailLink = address => <MailtoLink to={address}>{address}</MailtoLink>;
|
const emailLink = address => address && <MailtoLink to={address}>{address}</MailtoLink>;
|
||||||
|
|
||||||
if (certificate.isRestricted) {
|
if (certificate.isRestricted) {
|
||||||
return (
|
return (
|
||||||
<Banner variant="danger">
|
<Banner variant="danger">
|
||||||
{ supportEmail ? formatMessage(messages.certRestricted, { supportEmail: emailLink(supportEmail) }) : formatMessage(messages.certRestrictedNoEmail)}
|
{formatMessage(messages.certRestricted, { supportEmail: emailLink(supportEmail) })}
|
||||||
{isVerified && ' '}
|
{isVerified && ' '}
|
||||||
{isVerified && (billingEmail ? formatMessage(messages.certRefundContactBilling, { billingEmail: emailLink(billingEmail) }) : formatMessage(messages.certRefundContactBillingNoEmail))}
|
{isVerified && formatMessage(
|
||||||
|
messages.certRefundContactBilling,
|
||||||
|
{ billingEmail: emailLink(billingEmail) },
|
||||||
|
)}
|
||||||
</Banner>
|
</Banner>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import CertificateBanner from './CertificateBanner';
|
import CertificateBanner from './CertificateBanner';
|
||||||
@@ -21,6 +21,10 @@ jest.mock('components/Banner', () => 'Banner');
|
|||||||
|
|
||||||
describe('CertificateBanner', () => {
|
describe('CertificateBanner', () => {
|
||||||
const props = { cardId: 'cardId' };
|
const props = { cardId: 'cardId' };
|
||||||
|
reduxHooks.usePlatformSettingsData.mockReturnValue({
|
||||||
|
supportEmail: 'suport@email',
|
||||||
|
billingEmail: 'billing@email',
|
||||||
|
});
|
||||||
reduxHooks.useCardCourseRunData.mockReturnValue({
|
reduxHooks.useCardCourseRunData.mockReturnValue({
|
||||||
minPassingGrade: 0.8,
|
minPassingGrade: 0.8,
|
||||||
progressUrl: 'progressUrl',
|
progressUrl: 'progressUrl',
|
||||||
@@ -38,19 +42,16 @@ describe('CertificateBanner', () => {
|
|||||||
};
|
};
|
||||||
const defaultCourseRun = { isArchived: false };
|
const defaultCourseRun = { isArchived: false };
|
||||||
const defaultGrade = { isPassing: false };
|
const defaultGrade = { isPassing: false };
|
||||||
const defaultPlatformSettings = {};
|
|
||||||
const createWrapper = ({
|
const createWrapper = ({
|
||||||
certificate = {},
|
certificate = {},
|
||||||
enrollment = {},
|
enrollment = {},
|
||||||
grade = {},
|
grade = {},
|
||||||
courseRun = {},
|
courseRun = {},
|
||||||
platformSettings = {},
|
|
||||||
}) => {
|
}) => {
|
||||||
reduxHooks.useCardGradeData.mockReturnValueOnce({ ...defaultGrade, ...grade });
|
reduxHooks.useCardGradeData.mockReturnValueOnce({ ...defaultGrade, ...grade });
|
||||||
reduxHooks.useCardCertificateData.mockReturnValueOnce({ ...defaultCertificate, ...certificate });
|
reduxHooks.useCardCertificateData.mockReturnValueOnce({ ...defaultCertificate, ...certificate });
|
||||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ ...defaultEnrollment, ...enrollment });
|
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ ...defaultEnrollment, ...enrollment });
|
||||||
reduxHooks.useCardCourseRunData.mockReturnValueOnce({ ...defaultCourseRun, ...courseRun });
|
reduxHooks.useCardCourseRunData.mockReturnValueOnce({ ...defaultCourseRun, ...courseRun });
|
||||||
reduxHooks.usePlatformSettingsData.mockReturnValueOnce({ ...defaultPlatformSettings, ...platformSettings });
|
|
||||||
return shallow(<CertificateBanner {...props} />);
|
return shallow(<CertificateBanner {...props} />);
|
||||||
};
|
};
|
||||||
/** TODO: Update tests to validate snapshots **/
|
/** TODO: Update tests to validate snapshots **/
|
||||||
@@ -61,29 +62,7 @@ describe('CertificateBanner', () => {
|
|||||||
isRestricted: true,
|
isRestricted: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
|
||||||
test('is restricted with support email', () => {
|
|
||||||
const wrapper = createWrapper({
|
|
||||||
certificate: {
|
|
||||||
isRestricted: true,
|
|
||||||
},
|
|
||||||
platformSettings: {
|
|
||||||
supportEmail: 'suport@email',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
test('is restricted with billing email', () => {
|
|
||||||
const wrapper = createWrapper({
|
|
||||||
certificate: {
|
|
||||||
isRestricted: true,
|
|
||||||
},
|
|
||||||
platformSettings: {
|
|
||||||
billingEmail: 'billing@email',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
|
||||||
});
|
});
|
||||||
test('is restricted and verified', () => {
|
test('is restricted and verified', () => {
|
||||||
const wrapper = createWrapper({
|
const wrapper = createWrapper({
|
||||||
@@ -94,64 +73,21 @@ describe('CertificateBanner', () => {
|
|||||||
isVerified: true,
|
isVerified: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
|
||||||
test('is restricted and verified with support email', () => {
|
|
||||||
const wrapper = createWrapper({
|
|
||||||
certificate: {
|
|
||||||
isRestricted: true,
|
|
||||||
},
|
|
||||||
enrollment: {
|
|
||||||
isVerified: true,
|
|
||||||
},
|
|
||||||
platformSettings: {
|
|
||||||
supportEmail: 'suport@email',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
test('is restricted and verified with billing email', () => {
|
|
||||||
const wrapper = createWrapper({
|
|
||||||
certificate: {
|
|
||||||
isRestricted: true,
|
|
||||||
},
|
|
||||||
enrollment: {
|
|
||||||
isVerified: true,
|
|
||||||
},
|
|
||||||
platformSettings: {
|
|
||||||
billingEmail: 'billing@email',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
test('is restricted and verified with support and billing email', () => {
|
|
||||||
const wrapper = createWrapper({
|
|
||||||
certificate: {
|
|
||||||
isRestricted: true,
|
|
||||||
},
|
|
||||||
enrollment: {
|
|
||||||
isVerified: true,
|
|
||||||
},
|
|
||||||
platformSettings: {
|
|
||||||
supportEmail: 'suport@email',
|
|
||||||
billingEmail: 'billing@email',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
|
||||||
});
|
});
|
||||||
test('is passing and is downloadable', () => {
|
test('is passing and is downloadable', () => {
|
||||||
const wrapper = createWrapper({
|
const wrapper = createWrapper({
|
||||||
grade: { isPassing: true },
|
grade: { isPassing: true },
|
||||||
certificate: { isDownloadable: true },
|
certificate: { isDownloadable: true },
|
||||||
});
|
});
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
test('not passing and is downloadable', () => {
|
test('not passing and is downloadable', () => {
|
||||||
const wrapper = createWrapper({
|
const wrapper = createWrapper({
|
||||||
grade: { isPassing: false },
|
grade: { isPassing: false },
|
||||||
certificate: { isDownloadable: true },
|
certificate: { isDownloadable: true },
|
||||||
});
|
});
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
test('not passing and audit', () => {
|
test('not passing and audit', () => {
|
||||||
const wrapper = createWrapper({
|
const wrapper = createWrapper({
|
||||||
@@ -159,17 +95,17 @@ describe('CertificateBanner', () => {
|
|||||||
isAudit: true,
|
isAudit: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
test('not passing and has finished', () => {
|
test('not passing and has finished', () => {
|
||||||
const wrapper = createWrapper({
|
const wrapper = createWrapper({
|
||||||
courseRun: { isArchived: true },
|
courseRun: { isArchived: true },
|
||||||
});
|
});
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
test('not passing and not audit and not finished', () => {
|
test('not passing and not audit and not finished', () => {
|
||||||
const wrapper = createWrapper({});
|
const wrapper = createWrapper({});
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
test('is passing and is earned but unavailable', () => {
|
test('is passing and is earned but unavailable', () => {
|
||||||
const wrapper = createWrapper({
|
const wrapper = createWrapper({
|
||||||
@@ -180,7 +116,7 @@ describe('CertificateBanner', () => {
|
|||||||
isEarnedButUnavailable: true,
|
isEarnedButUnavailable: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
test('is passing and not downloadable render empty', () => {
|
test('is passing and not downloadable render empty', () => {
|
||||||
const wrapper = createWrapper({
|
const wrapper = createWrapper({
|
||||||
@@ -188,7 +124,7 @@ describe('CertificateBanner', () => {
|
|||||||
isPassing: true,
|
isPassing: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('behavior', () => {
|
describe('behavior', () => {
|
||||||
@@ -197,12 +133,8 @@ describe('CertificateBanner', () => {
|
|||||||
certificate: {
|
certificate: {
|
||||||
isRestricted: true,
|
isRestricted: true,
|
||||||
},
|
},
|
||||||
platformSettings: {
|
|
||||||
supportEmail: 'suport@email',
|
|
||||||
billingEmail: 'billing@email',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
const bannerMessage = wrapper.instance.findByType('format-message-function').map(el => el.props.message.defaultMessage).join('\n');
|
const bannerMessage = wrapper.find('format-message-function').map(el => el.prop('message').defaultMessage).join('\n');
|
||||||
expect(bannerMessage).toEqual(messages.certRestricted.defaultMessage);
|
expect(bannerMessage).toEqual(messages.certRestricted.defaultMessage);
|
||||||
expect(bannerMessage).toContain(messages.certRestricted.defaultMessage);
|
expect(bannerMessage).toContain(messages.certRestricted.defaultMessage);
|
||||||
});
|
});
|
||||||
@@ -214,12 +146,8 @@ describe('CertificateBanner', () => {
|
|||||||
enrollment: {
|
enrollment: {
|
||||||
isVerified: true,
|
isVerified: true,
|
||||||
},
|
},
|
||||||
platformSettings: {
|
|
||||||
supportEmail: 'suport@email',
|
|
||||||
billingEmail: 'billing@email',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
const bannerMessage = wrapper.instance.findByType('format-message-function').map(el => el.props.message.defaultMessage).join('\n');
|
const bannerMessage = wrapper.find('format-message-function').map(el => el.prop('message').defaultMessage).join('\n');
|
||||||
expect(bannerMessage).toContain(messages.certRestricted.defaultMessage);
|
expect(bannerMessage).toContain(messages.certRestricted.defaultMessage);
|
||||||
expect(bannerMessage).toContain(messages.certRefundContactBilling.defaultMessage);
|
expect(bannerMessage).toContain(messages.certRefundContactBilling.defaultMessage);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable max-len */
|
/* eslint-disable max-len */
|
||||||
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 '@edx/paragon';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { utilHooks, reduxHooks } from 'hooks';
|
import { utilHooks, reduxHooks } from 'hooks';
|
||||||
@@ -12,6 +12,7 @@ export const CourseBanner = ({ cardId }) => {
|
|||||||
const {
|
const {
|
||||||
isVerified,
|
isVerified,
|
||||||
isAuditAccessExpired,
|
isAuditAccessExpired,
|
||||||
|
canUpgrade,
|
||||||
coursewareAccess = {},
|
coursewareAccess = {},
|
||||||
} = reduxHooks.useCardEnrollmentData(cardId);
|
} = reduxHooks.useCardEnrollmentData(cardId);
|
||||||
const courseRun = reduxHooks.useCardCourseRunData(cardId);
|
const courseRun = reduxHooks.useCardCourseRunData(cardId);
|
||||||
@@ -25,7 +26,13 @@ export const CourseBanner = ({ cardId }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isAuditAccessExpired
|
{isAuditAccessExpired
|
||||||
&& (
|
&& (canUpgrade ? (
|
||||||
|
<Banner>
|
||||||
|
{formatMessage(messages.auditAccessExpired)}
|
||||||
|
{' '}
|
||||||
|
{formatMessage(messages.upgradeToAccess)}
|
||||||
|
</Banner>
|
||||||
|
) : (
|
||||||
<Banner>
|
<Banner>
|
||||||
{formatMessage(messages.auditAccessExpired)}
|
{formatMessage(messages.auditAccessExpired)}
|
||||||
{' '}
|
{' '}
|
||||||
@@ -33,7 +40,17 @@ export const CourseBanner = ({ cardId }) => {
|
|||||||
{formatMessage(messages.findAnotherCourse)}
|
{formatMessage(messages.findAnotherCourse)}
|
||||||
</Hyperlink>
|
</Hyperlink>
|
||||||
</Banner>
|
</Banner>
|
||||||
)}
|
))}
|
||||||
|
|
||||||
|
{courseRun.isActive && !canUpgrade && (
|
||||||
|
<Banner>
|
||||||
|
{formatMessage(messages.upgradeDeadlinePassed)}
|
||||||
|
{' '}
|
||||||
|
<Hyperlink isInline destination={courseRun.marketingUrl || ''}>
|
||||||
|
{formatMessage(messages.exploreCourseDetails)}
|
||||||
|
</Hyperlink>
|
||||||
|
</Banner>
|
||||||
|
)}
|
||||||
|
|
||||||
{(!isStaff && isTooEarly && courseRun.startDate) && (
|
{(!isStaff && isTooEarly && courseRun.startDate) && (
|
||||||
<Banner>
|
<Banner>
|
||||||
@@ -42,7 +59,6 @@ export const CourseBanner = ({ cardId }) => {
|
|||||||
})}
|
})}
|
||||||
</Banner>
|
</Banner>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(!isStaff && hasUnmetPrerequisites) && (
|
{(!isStaff && hasUnmetPrerequisites) && (
|
||||||
<Banner>{formatMessage(messages.prerequisitesNotMet)}</Banner>
|
<Banner>{formatMessage(messages.prerequisitesNotMet)}</Banner>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
import { Hyperlink } from '@openedx/paragon';
|
import { Hyperlink } from '@edx/paragon';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import { formatMessage } from 'testUtils';
|
import { formatMessage } from 'testUtils';
|
||||||
@@ -25,6 +25,7 @@ let el;
|
|||||||
|
|
||||||
const enrollmentData = {
|
const enrollmentData = {
|
||||||
isVerified: false,
|
isVerified: false,
|
||||||
|
canUpgrade: false,
|
||||||
isAuditAccessExpired: false,
|
isAuditAccessExpired: false,
|
||||||
coursewareAccess: {
|
coursewareAccess: {
|
||||||
hasUnmetPrerequisites: false,
|
hasUnmetPrerequisites: false,
|
||||||
@@ -64,27 +65,60 @@ describe('CourseBanner', () => {
|
|||||||
render({ enrollment: { isVerified: true } });
|
render({ enrollment: { isVerified: true } });
|
||||||
expect(el.isEmptyRender()).toEqual(true);
|
expect(el.isEmptyRender()).toEqual(true);
|
||||||
});
|
});
|
||||||
describe('audit access expired', () => {
|
describe('audit access expired, can upgrade', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render({ enrollment: { isAuditAccessExpired: true, canUpgrade: true } });
|
||||||
|
});
|
||||||
|
test('snapshot: (auditAccessExpired, upgradeToAccess)', () => {
|
||||||
|
expect(el).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
test('messages: (auditAccessExpired, upgradeToAccess)', () => {
|
||||||
|
expect(el.text()).toContain(messages.auditAccessExpired.defaultMessage);
|
||||||
|
expect(el.text()).toContain(messages.upgradeToAccess.defaultMessage);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('audit access expired, cannot upgrade', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
render({ enrollment: { isAuditAccessExpired: true } });
|
render({ enrollment: { isAuditAccessExpired: true } });
|
||||||
});
|
});
|
||||||
test('snapshot: (auditAccessExpired, findAnotherCourse hyperlink)', () => {
|
test('snapshot: (auditAccessExpired, findAnotherCourse hyperlink)', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
expect(el).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
test('messages: auditAccessExpired', () => {
|
test('messages: (auditAccessExpired, upgradeToAccess)', () => {
|
||||||
expect(el.instance.children[0].children[0].el).toContain(messages.auditAccessExpired.defaultMessage);
|
expect(el.text()).toContain(messages.auditAccessExpired.defaultMessage);
|
||||||
expect(el.instance.findByType(Hyperlink)[0].children[0].el).toEqual(messages.findAnotherCourse.defaultMessage);
|
expect(el.find(Hyperlink).text()).toEqual(messages.findAnotherCourse.defaultMessage);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('course run active and cannot upgrade', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render({ courseRun: { isActive: true } });
|
||||||
|
});
|
||||||
|
test('snapshot: (upgradseDeadlinePassed, exploreCourseDetails hyperlink)', () => {
|
||||||
|
expect(el).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
test('messages: (upgradseDeadlinePassed, exploreCourseDetails hyperlink)', () => {
|
||||||
|
expect(el.text()).toContain(messages.upgradeDeadlinePassed.defaultMessage);
|
||||||
|
const link = el.find(Hyperlink);
|
||||||
|
expect(link.text()).toEqual(messages.exploreCourseDetails.defaultMessage);
|
||||||
|
expect(link.props().destination).toEqual(courseRunData.marketingUrl);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('no display if audit access not expired and (course is not active or can upgrade)', () => {
|
||||||
|
render();
|
||||||
|
// isEmptyRender() isn't true because the minimal is <Fragment />
|
||||||
|
expect(el.html()).toEqual('');
|
||||||
|
render({ enrollment: { canUpgrade: true }, courseRun: { isActive: true } });
|
||||||
|
expect(el.html()).toEqual('');
|
||||||
|
});
|
||||||
describe('unmet prerequisites', () => {
|
describe('unmet prerequisites', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
render({ enrollment: { coursewareAccess: { hasUnmetPrerequisites: true } } });
|
render({ enrollment: { coursewareAccess: { hasUnmetPrerequisites: true } } });
|
||||||
});
|
});
|
||||||
test('snapshot: unmetPrerequisites', () => {
|
test('snapshot: unmetPrerequisites', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
expect(el).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
test('messages: prerequisitesNotMet', () => {
|
test('messages: prerequisitesNotMet', () => {
|
||||||
expect(el.instance.children[0].children[0].el).toContain(messages.prerequisitesNotMet.defaultMessage);
|
expect(el.text()).toContain(messages.prerequisitesNotMet.defaultMessage);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('too early', () => {
|
describe('too early', () => {
|
||||||
@@ -92,17 +126,17 @@ describe('CourseBanner', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
render({ enrollment: { coursewareAccess: { isTooEarly: true } }, courseRun: { startDate: null } });
|
render({ enrollment: { coursewareAccess: { isTooEarly: true } }, courseRun: { startDate: null } });
|
||||||
});
|
});
|
||||||
test('snapshot', () => expect(el.snapshot).toMatchSnapshot());
|
test('snapshot', () => expect(el).toMatchSnapshot());
|
||||||
test('messages', () => expect(el.instance.children).toEqual([]));
|
test('messages', () => expect(el.text()).toEqual(''));
|
||||||
});
|
});
|
||||||
describe('has start date', () => {
|
describe('has start date', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
render({ enrollment: { coursewareAccess: { isTooEarly: true } } });
|
render({ enrollment: { coursewareAccess: { isTooEarly: true } } });
|
||||||
});
|
});
|
||||||
test('snapshot', () => expect(el.snapshot).toMatchSnapshot());
|
test('snapshot', () => expect(el).toMatchSnapshot());
|
||||||
|
|
||||||
test('messages: courseHasNotStarted', () => {
|
test('messages: courseHasNotStarted', () => {
|
||||||
expect(el.instance.children[0].children[0].el).toContain(
|
expect(el.text()).toContain(
|
||||||
formatMessage(messages.courseHasNotStarted, { startDate: courseRunData.startDate }),
|
formatMessage(messages.courseHasNotStarted, { startDate: courseRunData.startDate }),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -113,7 +147,7 @@ describe('CourseBanner', () => {
|
|||||||
render({ enrollment: { coursewareAccess: { isStaff: true } } });
|
render({ enrollment: { coursewareAccess: { isStaff: true } } });
|
||||||
});
|
});
|
||||||
test('snapshot: isStaff', () => {
|
test('snapshot: isStaff', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
expect(el).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
test('snapshot: stacking banners', () => {
|
test('snapshot: stacking banners', () => {
|
||||||
@@ -126,6 +160,6 @@ describe('CourseBanner', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
expect(el).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,23 +6,20 @@ exports[`CreditBanner component render with error state snapshot 1`] = `
|
|||||||
>
|
>
|
||||||
<p
|
<p
|
||||||
className="credit-error-msg"
|
className="credit-error-msg"
|
||||||
data-testid="credit-error-msg"
|
|
||||||
>
|
>
|
||||||
<format-message-function
|
<format-message-function
|
||||||
message={
|
message={
|
||||||
{
|
Object {
|
||||||
"defaultMessage": "An error occurred with this transaction. For help, contact {supportEmailLink}.",
|
"defaultMessage": "An error occurred with this transaction. For help, contact {supportEmailLink}.",
|
||||||
"description": "",
|
"description": "",
|
||||||
"id": "learner-dash.courseCard.banners.credit.error",
|
"id": "learner-dash.courseCard.banners.credit.error",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
values={
|
values={
|
||||||
{
|
Object {
|
||||||
"supportEmailLink": <MailtoLink
|
"supportEmailLink": <EmailLink
|
||||||
to="test-support-email"
|
address="test-support-email"
|
||||||
>
|
/>,
|
||||||
test-support-email
|
|
||||||
</MailtoLink>,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -33,22 +30,6 @@ exports[`CreditBanner component render with error state snapshot 1`] = `
|
|||||||
</Banner>
|
</Banner>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`CreditBanner component render with error state with no email snapshot 1`] = `
|
|
||||||
<Banner
|
|
||||||
variant="danger"
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
className="credit-error-msg"
|
|
||||||
data-testid="credit-error-msg"
|
|
||||||
>
|
|
||||||
An error occurred with this transaction.
|
|
||||||
</p>
|
|
||||||
<ContentComponent
|
|
||||||
cardId="test-card-id"
|
|
||||||
/>
|
|
||||||
</Banner>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`CreditBanner component render with no error state snapshot 1`] = `
|
exports[`CreditBanner component render with no error state snapshot 1`] = `
|
||||||
<Banner>
|
<Banner>
|
||||||
<ContentComponent
|
<ContentComponent
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import PropTypes from 'prop-types';
|
|||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import Banner from 'components/Banner';
|
import Banner from 'components/Banner';
|
||||||
|
import EmailLink from 'components/EmailLink';
|
||||||
|
|
||||||
import { MailtoLink } from '@openedx/paragon';
|
|
||||||
import hooks from './hooks';
|
import hooks from './hooks';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
@@ -15,14 +15,13 @@ export const CreditBanner = ({ cardId }) => {
|
|||||||
if (hookData === null) {
|
if (hookData === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { ContentComponent, error, supportEmail } = hookData;
|
const { ContentComponent, error, supportEmail } = hookData;
|
||||||
const supportEmailLink = (<MailtoLink to={supportEmail}>{supportEmail}</MailtoLink>);
|
const supportEmailLink = (<EmailLink address={supportEmail} />);
|
||||||
return (
|
return (
|
||||||
<Banner {...(error && { variant: 'danger' })}>
|
<Banner {...(error && { variant: 'danger' })}>
|
||||||
{error && (
|
{error && (
|
||||||
<p className="credit-error-msg" data-testid="credit-error-msg">
|
<p className="credit-error-msg">
|
||||||
{supportEmail ? formatMessage(messages.error, { supportEmailLink }) : formatMessage(messages.errorNoEmail)}
|
{formatMessage(messages.error, { supportEmailLink })}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<ContentComponent cardId={cardId} />
|
<ContentComponent cardId={cardId} />
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { formatMessage } from 'testUtils';
|
import { formatMessage } from 'testUtils';
|
||||||
import { MailtoLink } from '@openedx/paragon';
|
|
||||||
|
import EmailLink from 'components/EmailLink';
|
||||||
|
|
||||||
import hooks from './hooks';
|
import hooks from './hooks';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
import CreditBanner from '.';
|
import CreditBanner from '.';
|
||||||
|
|
||||||
jest.mock('components/Banner', () => 'Banner');
|
jest.mock('components/Banner', () => 'Banner');
|
||||||
|
jest.mock('components/EmailLink', () => 'EmailLink');
|
||||||
|
|
||||||
jest.mock('./hooks', () => ({
|
jest.mock('./hooks', () => ({
|
||||||
useCreditBannerData: jest.fn(),
|
useCreditBannerData: jest.fn(),
|
||||||
@@ -44,37 +46,22 @@ describe('CreditBanner component', () => {
|
|||||||
el = shallow(<CreditBanner cardId={cardId} />);
|
el = shallow(<CreditBanner cardId={cardId} />);
|
||||||
});
|
});
|
||||||
test('snapshot', () => {
|
test('snapshot', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
expect(el).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
it('passes danger variant to Banner parent', () => {
|
it('passes danger variant to Banner parent', () => {
|
||||||
expect(el.instance.findByType('Banner')[0].props.variant).toEqual('danger');
|
expect(el.find('Banner').props().variant).toEqual('danger');
|
||||||
});
|
});
|
||||||
it('includes credit-error-msg with support email link', () => {
|
it('includes credit-error-msg with support email link', () => {
|
||||||
expect(el.instance.findByTestId('credit-error-msg')[0].children[0].el).toEqual(shallow(formatMessage(messages.error, {
|
expect(el.find('.credit-error-msg').containsMatchingElement(
|
||||||
supportEmailLink: (<MailtoLink to={supportEmail}>{supportEmail}</MailtoLink>),
|
formatMessage(messages.error, {
|
||||||
})));
|
supportEmailLink: (<EmailLink address={supportEmail} />),
|
||||||
|
}),
|
||||||
|
)).toEqual(true);
|
||||||
});
|
});
|
||||||
it('loads ContentComponent with cardId', () => {
|
it('loads ContentComponent with cardId', () => {
|
||||||
expect(el.instance.findByType('ContentComponent')[0].props.cardId).toEqual(cardId);
|
expect(el.find('ContentComponent').props().cardId).toEqual(cardId);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with error state with no email', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
hooks.useCreditBannerData.mockReturnValue({
|
|
||||||
error: true,
|
|
||||||
ContentComponent,
|
|
||||||
});
|
|
||||||
el = shallow(<CreditBanner cardId={cardId} />);
|
|
||||||
});
|
|
||||||
test('snapshot', () => {
|
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
it('includes credit-error-msg without support email link', () => {
|
|
||||||
expect(el.instance.findByTestId('credit-error-msg')[0].children[0].el).toEqual(formatMessage(messages.errorNoEmail));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with no error state', () => {
|
describe('with no error state', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
hooks.useCreditBannerData.mockReturnValue({
|
hooks.useCreditBannerData.mockReturnValue({
|
||||||
@@ -85,10 +72,10 @@ describe('CreditBanner component', () => {
|
|||||||
el = shallow(<CreditBanner cardId={cardId} />);
|
el = shallow(<CreditBanner cardId={cardId} />);
|
||||||
});
|
});
|
||||||
test('snapshot', () => {
|
test('snapshot', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
expect(el).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
it('loads ContentComponent with cardId', () => {
|
it('loads ContentComponent with cardId', () => {
|
||||||
expect(el.instance.findByType('ContentComponent')[0].props.cardId).toEqual(cardId);
|
expect(el.find('ContentComponent').props().cardId).toEqual(cardId);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { StrictDict } from 'utils';
|
||||||
|
|
||||||
const messages = defineMessages({
|
export const messages = StrictDict({
|
||||||
error: {
|
error: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.error',
|
id: 'learner-dash.courseCard.banners.credit.error',
|
||||||
description: '',
|
description: '',
|
||||||
defaultMessage: 'An error occurred with this transaction. For help, contact {supportEmailLink}.',
|
defaultMessage: 'An error occurred with this transaction. For help, contact {supportEmailLink}.',
|
||||||
},
|
},
|
||||||
errorNoEmail: {
|
|
||||||
id: 'learner-dash.courseCard.banners.credit.errorNoEmail',
|
|
||||||
description: '',
|
|
||||||
defaultMessage: 'An error occurred with this transaction.',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default messages;
|
export default messages;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { formatMessage } from 'testUtils';
|
import { formatMessage } from 'testUtils';
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
@@ -38,19 +38,19 @@ describe('ApprovedContent component', () => {
|
|||||||
describe('rendered CreditContent component', () => {
|
describe('rendered CreditContent component', () => {
|
||||||
let component;
|
let component;
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
component = el.instance.findByType('CreditContent');
|
component = el.find('CreditContent');
|
||||||
});
|
});
|
||||||
test('action.href from credit.providerStatusUrl', () => {
|
test('action.href from credit.providerStatusUrl', () => {
|
||||||
expect(component[0].props.action.href).toEqual(credit.providerStatusUrl);
|
expect(component.props().action.href).toEqual(credit.providerStatusUrl);
|
||||||
});
|
});
|
||||||
test('action.message is formatted viewCredit message', () => {
|
test('action.message is formatted viewCredit message', () => {
|
||||||
expect(component[0].props.action.message).toEqual(formatMessage(messages.viewCredit));
|
expect(component.props().action.message).toEqual(formatMessage(messages.viewCredit));
|
||||||
});
|
});
|
||||||
test('action.disabled is false', () => {
|
test('action.disabled is false', () => {
|
||||||
expect(component[0].props.action.disabled).toEqual(false);
|
expect(component.props().action.disabled).toEqual(false);
|
||||||
});
|
});
|
||||||
test('message is formatted approved message', () => {
|
test('message is formatted approved message', () => {
|
||||||
expect(component[0].props.message).toEqual(formatMessage(
|
expect(component.props().message).toEqual(formatMessage(
|
||||||
messages.approved,
|
messages.approved,
|
||||||
{
|
{
|
||||||
congratulations: (<b>{formatMessage(messages.congratulations)}</b>),
|
congratulations: (<b>{formatMessage(messages.congratulations)}</b>),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import { formatMessage } from 'testUtils';
|
import { formatMessage } from 'testUtils';
|
||||||
@@ -36,7 +36,7 @@ const render = () => {
|
|||||||
el = shallow(<EligibleContent cardId={cardId} />);
|
el = shallow(<EligibleContent cardId={cardId} />);
|
||||||
};
|
};
|
||||||
const loadComponent = () => {
|
const loadComponent = () => {
|
||||||
component = el.instance.findByType('CreditContent');
|
component = el.find('CreditContent');
|
||||||
};
|
};
|
||||||
describe('EligibleContent component', () => {
|
describe('EligibleContent component', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -56,24 +56,24 @@ describe('EligibleContent component', () => {
|
|||||||
loadComponent();
|
loadComponent();
|
||||||
});
|
});
|
||||||
test('action.onClick sends credit purchase track event', () => {
|
test('action.onClick sends credit purchase track event', () => {
|
||||||
expect(component[0].props.action.onClick).toEqual(
|
expect(component.props().action.onClick).toEqual(
|
||||||
track.credit.purchase(courseId),
|
track.credit.purchase(courseId),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test('action.message is formatted getCredit message', () => {
|
test('action.message is formatted getCredit message', () => {
|
||||||
expect(component[0].props.action.message).toEqual(formatMessage(messages.getCredit));
|
expect(component.props().action.message).toEqual(formatMessage(messages.getCredit));
|
||||||
});
|
});
|
||||||
test('message is formatted eligible message if no provider', () => {
|
test('message is formatted eligible message if no provider', () => {
|
||||||
reduxHooks.useCardCreditData.mockReturnValueOnce({});
|
reduxHooks.useCardCreditData.mockReturnValueOnce({});
|
||||||
render();
|
render();
|
||||||
loadComponent();
|
loadComponent();
|
||||||
expect(component[0].props.message).toEqual(formatMessage(
|
expect(component.props().message).toEqual(formatMessage(
|
||||||
messages.eligible,
|
messages.eligible,
|
||||||
{ getCredit: (<b>{formatMessage(messages.getCredit)}</b>) },
|
{ getCredit: (<b>{formatMessage(messages.getCredit)}</b>) },
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
test('message is formatted eligible message if provider', () => {
|
test('message is formatted eligible message if provider', () => {
|
||||||
expect(component[0].props.message).toEqual(
|
expect(component.props().message).toEqual(
|
||||||
formatMessage(messages.eligibleFromProvider, { providerName: credit.providerName }),
|
formatMessage(messages.eligibleFromProvider, { providerName: credit.providerName }),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { formatMessage } from 'testUtils';
|
import { formatMessage } from 'testUtils';
|
||||||
|
|
||||||
@@ -45,21 +45,21 @@ describe('MustRequestContent component', () => {
|
|||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
describe('rendered CreditContent component', () => {
|
describe('rendered CreditContent component', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
component = el.instance.findByType('CreditContent');
|
component = el.find('CreditContent');
|
||||||
});
|
});
|
||||||
test('action.onClick calls createCreditRequest from useCreditRequestData hook', () => {
|
test('action.onClick calls createCreditRequest from useCreditRequestData hook', () => {
|
||||||
expect(component[0].props.action.onClick).toEqual(createCreditRequest);
|
expect(component.props().action.onClick).toEqual(createCreditRequest);
|
||||||
});
|
});
|
||||||
test('action.message is formatted requestCredit message', () => {
|
test('action.message is formatted requestCredit message', () => {
|
||||||
expect(component[0].props.action.message).toEqual(
|
expect(component.props().action.message).toEqual(
|
||||||
formatMessage(messages.requestCredit),
|
formatMessage(messages.requestCredit),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test('action.disabled is false', () => {
|
test('action.disabled is false', () => {
|
||||||
expect(component[0].props.action.disabled).toEqual(false);
|
expect(component.props().action.disabled).toEqual(false);
|
||||||
});
|
});
|
||||||
test('message is formatted mustRequest message', () => {
|
test('message is formatted mustRequest message', () => {
|
||||||
expect(component[0].props.message).toEqual(
|
expect(component.props().message).toEqual(
|
||||||
formatMessage(messages.mustRequest, {
|
formatMessage(messages.mustRequest, {
|
||||||
linkToProviderSite: <ProviderLink cardId={cardId} />,
|
linkToProviderSite: <ProviderLink cardId={cardId} />,
|
||||||
requestCredit: <b>{formatMessage(messages.requestCredit)}</b>,
|
requestCredit: <b>{formatMessage(messages.requestCredit)}</b>,
|
||||||
@@ -67,7 +67,7 @@ describe('MustRequestContent component', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
test('requestData drawn from useCreditRequestData hook', () => {
|
test('requestData drawn from useCreditRequestData hook', () => {
|
||||||
expect(component[0].props.requestData).toEqual(requestData);
|
expect(component.props().requestData).toEqual(requestData);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { formatMessage } from 'testUtils';
|
import { formatMessage } from 'testUtils';
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
@@ -40,21 +40,21 @@ describe('PendingContent component', () => {
|
|||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
describe('rendered CreditContent component', () => {
|
describe('rendered CreditContent component', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
component = el.instance.findByType('CreditContent');
|
component = el.find('CreditContent');
|
||||||
});
|
});
|
||||||
test('action.href will go to provider status site', () => {
|
test('action.href will go to provider status site', () => {
|
||||||
expect(component[0].props.action.href).toEqual(providerStatusUrl);
|
expect(component.props().action.href).toEqual(providerStatusUrl);
|
||||||
});
|
});
|
||||||
test('action.message is formatted requestCredit message', () => {
|
test('action.message is formatted requestCredit message', () => {
|
||||||
expect(component[0].props.action.message).toEqual(
|
expect(component.props().action.message).toEqual(
|
||||||
formatMessage(messages.viewDetails),
|
formatMessage(messages.viewDetails),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test('action.disabled is false', () => {
|
test('action.disabled is false', () => {
|
||||||
expect(component[0].props.action.disabled).toEqual(false);
|
expect(component.props().action.disabled).toEqual(false);
|
||||||
});
|
});
|
||||||
test('message is formatted pending message', () => {
|
test('message is formatted pending message', () => {
|
||||||
expect(component[0].props.message).toEqual(
|
expect(component.props().message).toEqual(
|
||||||
formatMessage(messages.received, { providerName }),
|
formatMessage(messages.received, { providerName }),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { formatMessage } from 'testUtils';
|
import { formatMessage } from 'testUtils';
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
@@ -25,7 +25,7 @@ reduxHooks.useCardCreditData.mockReturnValue(credit);
|
|||||||
let el;
|
let el;
|
||||||
let component;
|
let component;
|
||||||
const render = () => { el = shallow(<RejectedContent cardId={cardId} />); };
|
const render = () => { el = shallow(<RejectedContent cardId={cardId} />); };
|
||||||
const loadComponent = () => { component = el.instance.findByType('CreditContent'); };
|
const loadComponent = () => { component = el.find('CreditContent'); };
|
||||||
|
|
||||||
describe('RejectedContent component', () => {
|
describe('RejectedContent component', () => {
|
||||||
beforeEach(render);
|
beforeEach(render);
|
||||||
@@ -38,10 +38,10 @@ describe('RejectedContent component', () => {
|
|||||||
describe('rendered CreditContent component', () => {
|
describe('rendered CreditContent component', () => {
|
||||||
beforeAll(loadComponent);
|
beforeAll(loadComponent);
|
||||||
test('no action is passed', () => {
|
test('no action is passed', () => {
|
||||||
expect(component[0].props.action).toEqual(undefined);
|
expect(component.props().action).toEqual(undefined);
|
||||||
});
|
});
|
||||||
test('message is formatted rejected message', () => {
|
test('message is formatted rejected message', () => {
|
||||||
expect(component[0].props.message).toEqual(formatMessage(
|
expect(component.props().message).toEqual(formatMessage(
|
||||||
messages.rejected,
|
messages.rejected,
|
||||||
{
|
{
|
||||||
linkToProviderSite: <ProviderLink cardId={cardId} />,
|
linkToProviderSite: <ProviderLink cardId={cardId} />,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { ActionRow, Button } from '@openedx/paragon';
|
import { ActionRow, Button } from '@edx/paragon';
|
||||||
import CreditRequestForm from './CreditRequestForm';
|
import CreditRequestForm from './CreditRequestForm';
|
||||||
|
|
||||||
export const CreditContent = ({ action, message, requestData }) => (
|
export const CreditContent = ({ action, message, requestData }) => (
|
||||||
<>
|
<>
|
||||||
<div className="message-copy credit-msg" data-testid="credit-msg">
|
<div className="message-copy credit-msg">
|
||||||
{message}
|
{message}
|
||||||
</div>
|
</div>
|
||||||
{action && (
|
{action && (
|
||||||
@@ -21,7 +21,6 @@ export const CreditContent = ({ action, message, requestData }) => (
|
|||||||
variant="outline-primary"
|
variant="outline-primary"
|
||||||
className="border-gray-400"
|
className="border-gray-400"
|
||||||
onClick={action.onClick}
|
onClick={action.onClick}
|
||||||
data-testid="action-row-btn"
|
|
||||||
>
|
>
|
||||||
{action.message}
|
{action.message}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import CreditContent from './CreditContent';
|
import CreditContent from './CreditContent';
|
||||||
|
|
||||||
@@ -22,24 +22,24 @@ describe('CreditContent component', () => {
|
|||||||
el = shallow(<CreditContent {...props} />);
|
el = shallow(<CreditContent {...props} />);
|
||||||
});
|
});
|
||||||
test('snapshot', () => {
|
test('snapshot', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
expect(el).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
it('loads href, onClick, and message into action row button', () => {
|
it('loads href, onClick, and message into action row button', () => {
|
||||||
const buttonEl = el.instance.findByTestId('action-row-btn')[0];
|
const buttonEl = el.find('ActionRow Button');
|
||||||
expect(buttonEl.props.href).toEqual(action.href);
|
expect(buttonEl.props().href).toEqual(action.href);
|
||||||
expect(buttonEl.props.onClick).toEqual(action.onClick);
|
expect(buttonEl.props().onClick).toEqual(action.onClick);
|
||||||
expect(buttonEl.props.disabled).toEqual(action.disabled);
|
expect(buttonEl.props().disabled).toEqual(action.disabled);
|
||||||
expect(buttonEl.children[0].el).toEqual(action.message);
|
expect(buttonEl.text()).toEqual(action.message);
|
||||||
});
|
});
|
||||||
it('loads message into credit-msg div', () => {
|
it('loads message into credit-msg div', () => {
|
||||||
expect(el.instance.findByTestId('credit-msg')[0].children[0].el).toEqual(message);
|
expect(el.find('div.credit-msg').text()).toEqual(message);
|
||||||
});
|
});
|
||||||
it('loads CreditRequestForm with passed requestData', () => {
|
it('loads CreditRequestForm with passed requestData', () => {
|
||||||
expect(el.instance.findByType('CreditRequestForm')[0].props.requestData).toEqual(requestData);
|
expect(el.find('CreditRequestForm').props().requestData).toEqual(requestData);
|
||||||
});
|
});
|
||||||
test('disables action button when action.disabled is true', () => {
|
test('disables action button when action.disabled is true', () => {
|
||||||
el = shallow(<CreditContent {...props} action={{ ...action, disabled: true }} />);
|
el.setProps({ action: { ...action, disabled: true } });
|
||||||
expect(el.instance.findByTestId('action-row-btn')[0].props.disabled).toEqual(true);
|
expect(el.find('ActionRow Button').props().disabled).toEqual(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('without action', () => {
|
describe('without action', () => {
|
||||||
@@ -47,13 +47,13 @@ describe('CreditContent component', () => {
|
|||||||
el = shallow(<CreditContent {...{ message, requestData }} />);
|
el = shallow(<CreditContent {...{ message, requestData }} />);
|
||||||
});
|
});
|
||||||
test('snapshot', () => {
|
test('snapshot', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
expect(el).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
it('loads message into credit-msg div', () => {
|
it('loads message into credit-msg div', () => {
|
||||||
expect(el.instance.findByTestId('credit-msg')[0].children[0].el).toEqual(message);
|
expect(el.find('div.credit-msg').text()).toEqual(message);
|
||||||
});
|
});
|
||||||
it('loads CreditRequestForm with passed requestData', () => {
|
it('loads CreditRequestForm with passed requestData', () => {
|
||||||
expect(el.instance.findByType('CreditRequestForm')[0].props.requestData).toEqual(requestData);
|
expect(el.find('CreditRequestForm').props().requestData).toEqual(requestData);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { Button, Form, FormControl } from '@openedx/paragon';
|
import { Button, Form, FormControl } from '@edx/paragon';
|
||||||
|
|
||||||
import useCreditRequestFormData from './hooks';
|
import useCreditRequestFormData from './hooks';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { keyStore } from 'utils';
|
import { keyStore } from 'utils';
|
||||||
|
|
||||||
@@ -46,19 +46,19 @@ describe('CreditRequestForm component', () => {
|
|||||||
shallowRender(requestData);
|
shallowRender(requestData);
|
||||||
});
|
});
|
||||||
test('snapshot', () => {
|
test('snapshot', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
expect(el).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
it('loads Form with requestData url', () => {
|
it('loads Form with requestData url', () => {
|
||||||
expect(el.instance.findByType('Form')[0].props.action).toEqual(requestData.url);
|
expect(el.find('Form').props().action).toEqual(requestData.url);
|
||||||
});
|
});
|
||||||
it('loads a textarea form control for each requestData parameter', () => {
|
it('loads a textarea form control for each requestData parameter', () => {
|
||||||
const controls = el.instance.findByType('FormControl');
|
const controls = el.find('FormControl');
|
||||||
expect(controls[0].props.name).toEqual(paramKeys.key1);
|
expect(controls.at(0).props().name).toEqual(paramKeys.key1);
|
||||||
expect(controls[0].props.value).toEqual(requestData.parameters.key1);
|
expect(controls.at(0).props().value).toEqual(requestData.parameters.key1);
|
||||||
expect(controls[1].props.name).toEqual(paramKeys.key2);
|
expect(controls.at(1).props().name).toEqual(paramKeys.key2);
|
||||||
expect(controls[1].props.value).toEqual(requestData.parameters.key2);
|
expect(controls.at(1).props().value).toEqual(requestData.parameters.key2);
|
||||||
expect(controls[2].props.name).toEqual(paramKeys.key3);
|
expect(controls.at(2).props().name).toEqual(paramKeys.key3);
|
||||||
expect(controls[2].props.value).toEqual(requestData.parameters.key3);
|
expect(controls.at(2).props().value).toEqual(requestData.parameters.key3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { render } from '@testing-library/react';
|
|||||||
import useCreditRequestFormData from './hooks';
|
import useCreditRequestFormData from './hooks';
|
||||||
import CreditRequestForm from '.';
|
import CreditRequestForm from '.';
|
||||||
|
|
||||||
jest.unmock('@openedx/paragon');
|
jest.unmock('@edx/paragon');
|
||||||
jest.unmock('react');
|
jest.unmock('react');
|
||||||
|
|
||||||
jest.mock('./hooks', () => ({
|
jest.mock('./hooks', () => ({
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ 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 '@edx/paragon';
|
||||||
|
|
||||||
export const ProviderLink = ({ cardId }) => {
|
export const ProviderLink = ({ cardId }) => {
|
||||||
const credit = reduxHooks.useCardCreditData(cardId);
|
const credit = reduxHooks.useCardCreditData(cardId);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
|
|
||||||
@@ -30,13 +30,13 @@ describe('ProviderLink component', () => {
|
|||||||
});
|
});
|
||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
test('snapshot', () => {
|
test('snapshot', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
expect(el).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
it('passes credit.providerStatusUrl to the hyperlink href', () => {
|
it('passes credit.providerStatusUrl to the hyperlink href', () => {
|
||||||
expect(el.instance.findByType('Hyperlink')[0].props.href).toEqual(credit.providerStatusUrl);
|
expect(el.find('Hyperlink').props().href).toEqual(credit.providerStatusUrl);
|
||||||
});
|
});
|
||||||
it('passes providerName for the link message', () => {
|
it('passes providerName for the link message', () => {
|
||||||
expect(el.instance.findByType('Hyperlink')[0].children[0].el).toEqual(credit.providerName);
|
expect(el.find('Hyperlink').text()).toEqual(credit.providerName);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ exports[`CreditContent component render with action snapshot 1`] = `
|
|||||||
<Fragment>
|
<Fragment>
|
||||||
<div
|
<div
|
||||||
className="message-copy credit-msg"
|
className="message-copy credit-msg"
|
||||||
data-testid="credit-msg"
|
|
||||||
>
|
>
|
||||||
test-message
|
test-message
|
||||||
</div>
|
</div>
|
||||||
@@ -14,7 +13,6 @@ exports[`CreditContent component render with action snapshot 1`] = `
|
|||||||
<Button
|
<Button
|
||||||
as="a"
|
as="a"
|
||||||
className="border-gray-400"
|
className="border-gray-400"
|
||||||
data-testid="action-row-btn"
|
|
||||||
disabled={false}
|
disabled={false}
|
||||||
href="test-action-href"
|
href="test-action-href"
|
||||||
onClick={[MockFunction test-action-onClick]}
|
onClick={[MockFunction test-action-onClick]}
|
||||||
@@ -27,8 +25,8 @@ exports[`CreditContent component render with action snapshot 1`] = `
|
|||||||
</ActionRow>
|
</ActionRow>
|
||||||
<CreditRequestForm
|
<CreditRequestForm
|
||||||
requestData={
|
requestData={
|
||||||
{
|
Object {
|
||||||
"parameters": {
|
"parameters": Object {
|
||||||
"key1": "val1",
|
"key1": "val1",
|
||||||
},
|
},
|
||||||
"url": "test-request-data-url",
|
"url": "test-request-data-url",
|
||||||
@@ -42,14 +40,13 @@ exports[`CreditContent component render without action snapshot 1`] = `
|
|||||||
<Fragment>
|
<Fragment>
|
||||||
<div
|
<div
|
||||||
className="message-copy credit-msg"
|
className="message-copy credit-msg"
|
||||||
data-testid="credit-msg"
|
|
||||||
>
|
>
|
||||||
test-message
|
test-message
|
||||||
</div>
|
</div>
|
||||||
<CreditRequestForm
|
<CreditRequestForm
|
||||||
requestData={
|
requestData={
|
||||||
{
|
Object {
|
||||||
"parameters": {
|
"parameters": Object {
|
||||||
"key1": "val1",
|
"key1": "val1",
|
||||||
},
|
},
|
||||||
"url": "test-request-data-url",
|
"url": "test-request-data-url",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { StrictDict } from 'utils';
|
||||||
|
|
||||||
const messages = defineMessages({
|
export const messages = StrictDict({
|
||||||
approved: {
|
approved: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.approved',
|
id: 'learner-dash.courseCard.banners.credit.approved',
|
||||||
description: '',
|
description: '',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { Button, MailtoLink } from '@openedx/paragon';
|
import { Button, MailtoLink } from '@edx/paragon';
|
||||||
|
|
||||||
import { utilHooks, reduxHooks } from 'hooks';
|
import { utilHooks, reduxHooks } from 'hooks';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import EntitlementBanner from './EntitlementBanner';
|
import EntitlementBanner from './EntitlementBanner';
|
||||||
@@ -50,11 +50,11 @@ describe('EntitlementBanner', () => {
|
|||||||
});
|
});
|
||||||
test('snapshot: no sessions available', () => {
|
test('snapshot: no sessions available', () => {
|
||||||
render({ entitlement: { isFulfilled: false, hasSessions: false } });
|
render({ entitlement: { isFulfilled: false, hasSessions: false } });
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
expect(el).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
test('snapshot: expiration warning', () => {
|
test('snapshot: expiration warning', () => {
|
||||||
render({ entitlement: { showExpirationWarning: true } });
|
render({ entitlement: { showExpirationWarning: true } });
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
expect(el).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
test('no display if sessions available and not displaying warning', () => {
|
test('no display if sessions available and not displaying warning', () => {
|
||||||
render();
|
render();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { ProgramsList } from './ProgramsList';
|
import { ProgramsList } from './ProgramsList';
|
||||||
|
|
||||||
@@ -16,8 +16,8 @@ describe('ProgramsList', () => {
|
|||||||
|
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
||||||
const wrapper = shallow(<ProgramsList programs={programs} />);
|
const wrapper = shallow(<ProgramsList programs={programs} />);
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
|
||||||
expect(wrapper.instance.findByType('li').length).toEqual(programs.length);
|
expect(wrapper.find('li').length).toEqual(programs.length);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ exports[`RelatedProgramsBanner render with programs 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
<ProgramsList
|
<ProgramsList
|
||||||
programs={
|
programs={
|
||||||
[
|
Array [
|
||||||
{
|
Object {
|
||||||
"title": "Program 1",
|
"title": "Program 1",
|
||||||
"url": "http://example.com/program1",
|
"url": "http://example.com/program1",
|
||||||
},
|
},
|
||||||
{
|
Object {
|
||||||
"title": "Program 2",
|
"title": "Program 2",
|
||||||
"url": "http://example.com/program2",
|
"url": "http://example.com/program2",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { Program } from '@openedx/paragon/icons';
|
import { Program } from '@edx/paragon/icons';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
@@ -15,20 +15,16 @@ export const RelatedProgramsBanner = ({ cardId }) => {
|
|||||||
|
|
||||||
const programData = reduxHooks.useCardRelatedProgramsData(cardId);
|
const programData = reduxHooks.useCardRelatedProgramsData(cardId);
|
||||||
|
|
||||||
if (!programData?.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Banner
|
programData?.length > 0 && (
|
||||||
icon={Program}
|
<Banner
|
||||||
className="bg-white border-top border-bottom mb-0 related-programs-banner"
|
icon={Program}
|
||||||
>
|
className="bg-white border-top border-bottom mb-0 related-programs-banner"
|
||||||
<span className="font-weight-bolder">
|
>
|
||||||
{formatMessage(messages.relatedPrograms)}
|
<span className="font-weight-bolder">{formatMessage(messages.relatedPrograms)}</span>
|
||||||
</span>
|
<ProgramList programs={programData.list} />
|
||||||
<ProgramList programs={programData.list} />
|
</Banner>
|
||||||
</Banner>
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
RelatedProgramsBanner.propTypes = {
|
RelatedProgramsBanner.propTypes = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
import RelatedProgramsBanner from '.';
|
import RelatedProgramsBanner from '.';
|
||||||
@@ -37,6 +37,6 @@ describe('RelatedProgramsBanner', () => {
|
|||||||
length: 2,
|
length: 2,
|
||||||
});
|
});
|
||||||
const el = shallow(<RelatedProgramsBanner cardId={cardId} />);
|
const el = shallow(<RelatedProgramsBanner cardId={cardId} />);
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
expect(el).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { StrictDict } from 'utils';
|
||||||
|
|
||||||
const messages = defineMessages({
|
export const messages = StrictDict({
|
||||||
relatedPrograms: {
|
relatedPrograms: {
|
||||||
id: 'learner-dash.courseCard.banners.relatedPrograms',
|
id: 'learner-dash.courseCard.banners.relatedPrograms',
|
||||||
description: 'title for related programs banner',
|
description: 'title for related programs banner',
|
||||||
|
|||||||
@@ -15,13 +15,30 @@ exports[`CertificateBanner snapshot is passing and is earned but unavailable 1`]
|
|||||||
</Banner>
|
</Banner>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`CertificateBanner snapshot is passing and not downloadable render empty 1`] = `null`;
|
exports[`CertificateBanner snapshot is passing and not downloadable render empty 1`] = `""`;
|
||||||
|
|
||||||
exports[`CertificateBanner snapshot is restricted 1`] = `
|
exports[`CertificateBanner snapshot is restricted 1`] = `
|
||||||
<Banner
|
<Banner
|
||||||
variant="danger"
|
variant="danger"
|
||||||
>
|
>
|
||||||
Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know.
|
<format-message-function
|
||||||
|
message={
|
||||||
|
Object {
|
||||||
|
"defaultMessage": "Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting {supportEmail}.",
|
||||||
|
"description": "Restricted certificate warning message",
|
||||||
|
"id": "learner-dash.courseCard.banners.certificateRestricted",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
values={
|
||||||
|
Object {
|
||||||
|
"supportEmail": <MailtoLink
|
||||||
|
to="suport@email"
|
||||||
|
>
|
||||||
|
suport@email
|
||||||
|
</MailtoLink>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Banner>
|
</Banner>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -29,28 +46,35 @@ exports[`CertificateBanner snapshot is restricted and verified 1`] = `
|
|||||||
<Banner
|
<Banner
|
||||||
variant="danger"
|
variant="danger"
|
||||||
>
|
>
|
||||||
Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know.
|
<format-message-function
|
||||||
|
message={
|
||||||
If you would like a refund on your Certificate of Achievement, please contact us.
|
Object {
|
||||||
</Banner>
|
"defaultMessage": "Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting {supportEmail}.",
|
||||||
`;
|
"description": "Restricted certificate warning message",
|
||||||
|
"id": "learner-dash.courseCard.banners.certificateRestricted",
|
||||||
exports[`CertificateBanner snapshot is restricted and verified with billing email 1`] = `
|
}
|
||||||
<Banner
|
}
|
||||||
variant="danger"
|
values={
|
||||||
>
|
Object {
|
||||||
Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know.
|
"supportEmail": <MailtoLink
|
||||||
|
to="suport@email"
|
||||||
|
>
|
||||||
|
suport@email
|
||||||
|
</MailtoLink>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<format-message-function
|
<format-message-function
|
||||||
message={
|
message={
|
||||||
{
|
Object {
|
||||||
"defaultMessage": "If you would like a refund on your Certificate of Achievement, please contact our billing address {billingEmail}",
|
"defaultMessage": "If you would like a refund on your Certificate of Achievement, please contact our billing address {billingEmail}",
|
||||||
"description": "Message to learners to contact billing for certificate refunds",
|
"description": "Message to learners to contact billing for certificate refunds",
|
||||||
"id": "learner-dash.courseCard.banners.certificateRefundContactBilling",
|
"id": "learner-dash.courseCard.banners.certificateRefundContactBilling",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
values={
|
values={
|
||||||
{
|
Object {
|
||||||
"billingEmail": <MailtoLink
|
"billingEmail": <MailtoLink
|
||||||
to="billing@email"
|
to="billing@email"
|
||||||
>
|
>
|
||||||
@@ -62,110 +86,6 @@ exports[`CertificateBanner snapshot is restricted and verified with billing emai
|
|||||||
</Banner>
|
</Banner>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`CertificateBanner snapshot is restricted and verified with support and billing email 1`] = `
|
|
||||||
<Banner
|
|
||||||
variant="danger"
|
|
||||||
>
|
|
||||||
<format-message-function
|
|
||||||
message={
|
|
||||||
{
|
|
||||||
"defaultMessage": "Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting {supportEmail}.",
|
|
||||||
"description": "Restricted certificate warning message",
|
|
||||||
"id": "learner-dash.courseCard.banners.certificateRestricted",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
values={
|
|
||||||
{
|
|
||||||
"supportEmail": <MailtoLink
|
|
||||||
to="suport@email"
|
|
||||||
>
|
|
||||||
suport@email
|
|
||||||
</MailtoLink>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<format-message-function
|
|
||||||
message={
|
|
||||||
{
|
|
||||||
"defaultMessage": "If you would like a refund on your Certificate of Achievement, please contact our billing address {billingEmail}",
|
|
||||||
"description": "Message to learners to contact billing for certificate refunds",
|
|
||||||
"id": "learner-dash.courseCard.banners.certificateRefundContactBilling",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
values={
|
|
||||||
{
|
|
||||||
"billingEmail": <MailtoLink
|
|
||||||
to="billing@email"
|
|
||||||
>
|
|
||||||
billing@email
|
|
||||||
</MailtoLink>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Banner>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`CertificateBanner snapshot is restricted and verified with support email 1`] = `
|
|
||||||
<Banner
|
|
||||||
variant="danger"
|
|
||||||
>
|
|
||||||
<format-message-function
|
|
||||||
message={
|
|
||||||
{
|
|
||||||
"defaultMessage": "Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting {supportEmail}.",
|
|
||||||
"description": "Restricted certificate warning message",
|
|
||||||
"id": "learner-dash.courseCard.banners.certificateRestricted",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
values={
|
|
||||||
{
|
|
||||||
"supportEmail": <MailtoLink
|
|
||||||
to="suport@email"
|
|
||||||
>
|
|
||||||
suport@email
|
|
||||||
</MailtoLink>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
If you would like a refund on your Certificate of Achievement, please contact us.
|
|
||||||
</Banner>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`CertificateBanner snapshot is restricted with billing email 1`] = `
|
|
||||||
<Banner
|
|
||||||
variant="danger"
|
|
||||||
>
|
|
||||||
Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know.
|
|
||||||
</Banner>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`CertificateBanner snapshot is restricted with support email 1`] = `
|
|
||||||
<Banner
|
|
||||||
variant="danger"
|
|
||||||
>
|
|
||||||
<format-message-function
|
|
||||||
message={
|
|
||||||
{
|
|
||||||
"defaultMessage": "Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting {supportEmail}.",
|
|
||||||
"description": "Restricted certificate warning message",
|
|
||||||
"id": "learner-dash.courseCard.banners.certificateRestricted",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
values={
|
|
||||||
{
|
|
||||||
"supportEmail": <MailtoLink
|
|
||||||
to="suport@email"
|
|
||||||
>
|
|
||||||
suport@email
|
|
||||||
</MailtoLink>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Banner>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`CertificateBanner snapshot not passing and audit 1`] = `
|
exports[`CertificateBanner snapshot not passing and audit 1`] = `
|
||||||
<Banner>
|
<Banner>
|
||||||
Grade required to pass the course: 0.8%
|
Grade required to pass the course: 0.8%
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`CourseBanner audit access expired snapshot: (auditAccessExpired, findAnotherCourse hyperlink) 1`] = `
|
exports[`CourseBanner audit access expired, can upgrade snapshot: (auditAccessExpired, upgradeToAccess) 1`] = `
|
||||||
|
<Fragment>
|
||||||
|
<Banner>
|
||||||
|
Your audit access to this course has expired.
|
||||||
|
|
||||||
|
Upgrade now to access your course again.
|
||||||
|
</Banner>
|
||||||
|
</Fragment>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`CourseBanner audit access expired, cannot upgrade snapshot: (auditAccessExpired, findAnotherCourse hyperlink) 1`] = `
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Banner>
|
<Banner>
|
||||||
Your audit access to this course has expired.
|
Your audit access to this course has expired.
|
||||||
@@ -15,6 +25,21 @@ exports[`CourseBanner audit access expired snapshot: (auditAccessExpired, findAn
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`CourseBanner course run active and cannot upgrade snapshot: (upgradseDeadlinePassed, exploreCourseDetails hyperlink) 1`] = `
|
||||||
|
<Fragment>
|
||||||
|
<Banner>
|
||||||
|
Your upgrade deadline for this course has passed. To upgrade, enroll in a session that is farther in the future.
|
||||||
|
|
||||||
|
<Hyperlink
|
||||||
|
destination="marketing-url"
|
||||||
|
isInline={true}
|
||||||
|
>
|
||||||
|
Explore course details.
|
||||||
|
</Hyperlink>
|
||||||
|
</Banner>
|
||||||
|
</Fragment>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`CourseBanner snapshot: stacking banners 1`] = `<Fragment />`;
|
exports[`CourseBanner snapshot: stacking banners 1`] = `<Fragment />`;
|
||||||
|
|
||||||
exports[`CourseBanner staff snapshot: isStaff 1`] = `<Fragment />`;
|
exports[`CourseBanner staff snapshot: isStaff 1`] = `<Fragment />`;
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ exports[`EntitlementBanner snapshot: expiration warning 1`] = `
|
|||||||
<Banner>
|
<Banner>
|
||||||
<format-message-function
|
<format-message-function
|
||||||
message={
|
message={
|
||||||
{
|
Object {
|
||||||
"defaultMessage": "You must {selectSessionButton} by {changeDeadline} to access the course.",
|
"defaultMessage": "You must {selectSessionButton} by {changeDeadline} to access the course.",
|
||||||
"description": "Entitlement course message when the entitlement is expiring soon.",
|
"description": "Entitlement course message when the entitlement is expiring soon.",
|
||||||
"id": "learner-dash.courseCard.banners.entitlementExpiringSoon",
|
"id": "learner-dash.courseCard.banners.entitlementExpiringSoon",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
values={
|
values={
|
||||||
{
|
Object {
|
||||||
"changeDeadline": "11/11/2022",
|
"changeDeadline": "11/11/2022",
|
||||||
"selectSessionButton": <Button
|
"selectSessionButton": <Button
|
||||||
className="m-0 p-0"
|
className="m-0 p-0"
|
||||||
@@ -33,14 +33,14 @@ exports[`EntitlementBanner snapshot: no sessions available 1`] = `
|
|||||||
>
|
>
|
||||||
<format-message-function
|
<format-message-function
|
||||||
message={
|
message={
|
||||||
{
|
Object {
|
||||||
"defaultMessage": "There are no sessions available at the moment. The course team will create new sessions soon. If no sessions appear, please contact {emailLink} for information.",
|
"defaultMessage": "There are no sessions available at the moment. The course team will create new sessions soon. If no sessions appear, please contact {emailLink} for information.",
|
||||||
"description": "Entitlement course message when no sessions are available",
|
"description": "Entitlement course message when no sessions are available",
|
||||||
"id": "learner-dash.courseCard.banners.entitlementUnavailable",
|
"id": "learner-dash.courseCard.banners.entitlementUnavailable",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
values={
|
values={
|
||||||
{
|
Object {
|
||||||
"emailLink": <MailtoLink
|
"emailLink": <MailtoLink
|
||||||
to="test-support-email"
|
to="test-support-email"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ exports[`CourseCardBanners render with isEnrolled false 1`] = `
|
|||||||
<RelatedProgramsBanner
|
<RelatedProgramsBanner
|
||||||
cardId="test-card-id"
|
cardId="test-card-id"
|
||||||
/>
|
/>
|
||||||
<CourseBannerSlot
|
<CourseBanner
|
||||||
cardId="test-card-id"
|
cardId="test-card-id"
|
||||||
/>
|
/>
|
||||||
<EntitlementBanner
|
<EntitlementBanner
|
||||||
@@ -25,7 +25,7 @@ exports[`CourseCardBanners renders default CourseCardBanners 1`] = `
|
|||||||
<RelatedProgramsBanner
|
<RelatedProgramsBanner
|
||||||
cardId="test-card-id"
|
cardId="test-card-id"
|
||||||
/>
|
/>
|
||||||
<CourseBannerSlot
|
<CourseBanner
|
||||||
cardId="test-card-id"
|
cardId="test-card-id"
|
||||||
/>
|
/>
|
||||||
<EntitlementBanner
|
<EntitlementBanner
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
|
|
||||||
import CourseBannerSlot from 'plugin-slots/CourseBannerSlot';
|
import CourseBanner from './CourseBanner';
|
||||||
import CertificateBanner from './CertificateBanner';
|
import CertificateBanner from './CertificateBanner';
|
||||||
import CreditBanner from './CreditBanner';
|
import CreditBanner from './CreditBanner';
|
||||||
import EntitlementBanner from './EntitlementBanner';
|
import EntitlementBanner from './EntitlementBanner';
|
||||||
@@ -14,7 +14,7 @@ export const CourseCardBanners = ({ cardId }) => {
|
|||||||
return (
|
return (
|
||||||
<div className="course-card-banners" data-testid="CourseCardBanners">
|
<div className="course-card-banners" data-testid="CourseCardBanners">
|
||||||
<RelatedProgramsBanner cardId={cardId} />
|
<RelatedProgramsBanner cardId={cardId} />
|
||||||
<CourseBannerSlot cardId={cardId} />
|
<CourseBanner cardId={cardId} />
|
||||||
<EntitlementBanner cardId={cardId} />
|
<EntitlementBanner cardId={cardId} />
|
||||||
{isEnrolled && <CertificateBanner cardId={cardId} />}
|
{isEnrolled && <CertificateBanner cardId={cardId} />}
|
||||||
{isEnrolled && <CreditBanner cardId={cardId} />}
|
{isEnrolled && <CreditBanner cardId={cardId} />}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
|
|
||||||
@@ -22,11 +22,11 @@ describe('CourseCardBanners', () => {
|
|||||||
};
|
};
|
||||||
test('renders default CourseCardBanners', () => {
|
test('renders default CourseCardBanners', () => {
|
||||||
const wrapper = shallow(<CourseCardBanners {...props} />);
|
const wrapper = shallow(<CourseCardBanners {...props} />);
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
test('render with isEnrolled false', () => {
|
test('render with isEnrolled false', () => {
|
||||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ isEnrolled: false });
|
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ isEnrolled: false });
|
||||||
const wrapper = shallow(<CourseCardBanners {...props} />);
|
const wrapper = shallow(<CourseCardBanners {...props} />);
|
||||||
expect(wrapper.snapshot).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,36 +1,41 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { StrictDict } from 'utils';
|
||||||
|
|
||||||
const messages = defineMessages({
|
export const messages = StrictDict({
|
||||||
auditAccessExpired: {
|
auditAccessExpired: {
|
||||||
id: 'learner-dash.courseCard.banners.auditAccessExpired',
|
id: 'learner-dash.courseCard.banners.auditAccessExpired',
|
||||||
description: 'Audit access expiration banner message',
|
description: 'Audit access expiration banner message',
|
||||||
defaultMessage: 'Your audit access to this course has expired.',
|
defaultMessage: 'Your audit access to this course has expired.',
|
||||||
},
|
},
|
||||||
|
upgradeToAccess: {
|
||||||
|
id: 'learner-dash.courseCard.banners.upgradeToAccess',
|
||||||
|
description: 'Upgrade prompt for audit-expired learners that can still upgrade',
|
||||||
|
defaultMessage: 'Upgrade now to access your course again.',
|
||||||
|
},
|
||||||
findAnotherCourse: {
|
findAnotherCourse: {
|
||||||
id: 'learner-dash.courseCard.banners.findAnotherCourse',
|
id: 'learner-dash.courseCard.banners.findAnotherCourse',
|
||||||
description: 'Action prompt taking learners to course exploration',
|
description: 'Action prompt taking learners to course exploration',
|
||||||
defaultMessage: 'Find another course',
|
defaultMessage: 'Find another course',
|
||||||
},
|
},
|
||||||
|
upgradeDeadlinePassed: {
|
||||||
|
id: 'learner-dash.courseCard.banners.upgradeDeadlinePassed',
|
||||||
|
description: 'Audit upgrade deadline passed banner message',
|
||||||
|
defaultMessage: 'Your upgrade deadline for this course has passed. To upgrade, enroll in a session that is farther in the future.',
|
||||||
|
},
|
||||||
|
exploreCourseDetails: {
|
||||||
|
id: 'learner-dash.courseCard.banners.exploreCourseDetails',
|
||||||
|
description: 'Action prompt taking learners to course details page',
|
||||||
|
defaultMessage: 'Explore course details.',
|
||||||
|
},
|
||||||
certRestricted: {
|
certRestricted: {
|
||||||
id: 'learner-dash.courseCard.banners.certificateRestricted',
|
id: 'learner-dash.courseCard.banners.certificateRestricted',
|
||||||
description: 'Restricted certificate warning message',
|
description: 'Restricted certificate warning message',
|
||||||
defaultMessage: 'Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting {supportEmail}.',
|
defaultMessage: 'Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting {supportEmail}.',
|
||||||
},
|
},
|
||||||
certRestrictedNoEmail: {
|
|
||||||
id: 'learner-dash.courseCard.banners.certificateRestrictedNoEmail',
|
|
||||||
description: 'Restricted certificate warning message',
|
|
||||||
defaultMessage: 'Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know.',
|
|
||||||
},
|
|
||||||
certRefundContactBilling: {
|
certRefundContactBilling: {
|
||||||
id: 'learner-dash.courseCard.banners.certificateRefundContactBilling',
|
id: 'learner-dash.courseCard.banners.certificateRefundContactBilling',
|
||||||
description: 'Message to learners to contact billing for certificate refunds',
|
description: 'Message to learners to contact billing for certificate refunds',
|
||||||
defaultMessage: 'If you would like a refund on your Certificate of Achievement, please contact our billing address {billingEmail}',
|
defaultMessage: 'If you would like a refund on your Certificate of Achievement, please contact our billing address {billingEmail}',
|
||||||
},
|
},
|
||||||
certRefundContactBillingNoEmail: {
|
|
||||||
id: 'learner-dash.courseCard.banners.certificateRefundContactBillingNoEmail',
|
|
||||||
description: 'Message to learners to contact billing for certificate refunds',
|
|
||||||
defaultMessage: 'If you would like a refund on your Certificate of Achievement, please contact us.',
|
|
||||||
},
|
|
||||||
passingGrade: {
|
passingGrade: {
|
||||||
id: 'learner-dash.courseCard.banners.passingGrade',
|
id: 'learner-dash.courseCard.banners.passingGrade',
|
||||||
description: 'Message to learners with minimum passing grade for the course',
|
description: 'Message to learners with minimum passing grade for the course',
|
||||||
|
|||||||
@@ -1,56 +1,54 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`CourseCard Details component does not have change session button on regular course 1`] = `
|
exports[`CourseCard Details component does not have change session button on regular course 1`] = `
|
||||||
<div>
|
<span
|
||||||
<span
|
className="small"
|
||||||
class="small"
|
data-testid="CourseCardDetails"
|
||||||
data-testid="CourseCardDetails"
|
>
|
||||||
>
|
provider-name
|
||||||
provider-name
|
•
|
||||||
•
|
test-course-number
|
||||||
test-course-number
|
• access-message
|
||||||
• access-message
|
</span>
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`CourseCard Details component has change session button on entitlement course 1`] = `
|
exports[`CourseCard Details component has change session button on entitlement course 1`] = `
|
||||||
<div>
|
<span
|
||||||
<span
|
className="small"
|
||||||
class="small"
|
data-testid="CourseCardDetails"
|
||||||
data-testid="CourseCardDetails"
|
>
|
||||||
|
provider-name
|
||||||
|
•
|
||||||
|
test-course-number
|
||||||
|
• access-message
|
||||||
|
•
|
||||||
|
<Button
|
||||||
|
className="m-0 p-0"
|
||||||
|
onClick={[MockFunction useSelectSession.openSessionModal]}
|
||||||
|
size="inline"
|
||||||
|
variant="link"
|
||||||
>
|
>
|
||||||
provider-name
|
change-or-leave-session-message
|
||||||
•
|
</Button>
|
||||||
test-course-number
|
</span>
|
||||||
• access-message
|
|
||||||
•
|
|
||||||
<button
|
|
||||||
class="m-0 p-0"
|
|
||||||
variant="link"
|
|
||||||
>
|
|
||||||
change-or-leave-session-message
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`CourseCard Details component has change session button on entitlement course but no access message 1`] = `
|
exports[`CourseCard Details component has change session button on entitlement course but no access message 1`] = `
|
||||||
<div>
|
<span
|
||||||
<span
|
className="small"
|
||||||
class="small"
|
data-testid="CourseCardDetails"
|
||||||
data-testid="CourseCardDetails"
|
>
|
||||||
|
provider-name
|
||||||
|
•
|
||||||
|
test-course-number
|
||||||
|
•
|
||||||
|
<Button
|
||||||
|
className="m-0 p-0"
|
||||||
|
onClick={[MockFunction useSelectSession.openSessionModal]}
|
||||||
|
size="inline"
|
||||||
|
variant="link"
|
||||||
>
|
>
|
||||||
provider-name
|
change-or-leave-session-message
|
||||||
•
|
</Button>
|
||||||
test-course-number
|
</span>
|
||||||
•
|
|
||||||
<button
|
|
||||||
class="m-0 p-0"
|
|
||||||
variant="link"
|
|
||||||
>
|
|
||||||
change-or-leave-session-message
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { Button } from '@openedx/paragon';
|
import { Button } from '@edx/paragon';
|
||||||
|
|
||||||
import useCardDetailsData from './hooks';
|
import useCardDetailsData from './hooks';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user