Compare commits
5 Commits
open-relea
...
ilee2u/bug
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55dd7a84c6 | ||
|
|
91ced0050d | ||
|
|
fa4cd420bf | ||
|
|
bc87466e6c | ||
|
|
6c4d2fc88a |
3
.env
3
.env
@@ -15,7 +15,6 @@ ECOMMERCE_BASE_URL=''
|
|||||||
ENABLE_JUMPNAV='true'
|
ENABLE_JUMPNAV='true'
|
||||||
ENABLE_NOTICES=''
|
ENABLE_NOTICES=''
|
||||||
ENTERPRISE_LEARNER_PORTAL_HOSTNAME=''
|
ENTERPRISE_LEARNER_PORTAL_HOSTNAME=''
|
||||||
EXAMS_BASE_URL=''
|
|
||||||
FAVICON_URL=''
|
FAVICON_URL=''
|
||||||
IGNORED_ERROR_REGEX=''
|
IGNORED_ERROR_REGEX=''
|
||||||
INSIGHTS_BASE_URL=''
|
INSIGHTS_BASE_URL=''
|
||||||
@@ -29,8 +28,6 @@ LOGO_WHITE_URL=''
|
|||||||
LEGACY_THEME_NAME=''
|
LEGACY_THEME_NAME=''
|
||||||
MARKETING_SITE_BASE_URL=''
|
MARKETING_SITE_BASE_URL=''
|
||||||
ORDER_HISTORY_URL=''
|
ORDER_HISTORY_URL=''
|
||||||
PROCTORED_EXAM_FAQ_URL=''
|
|
||||||
PROCTORED_EXAM_RULES_URL=''
|
|
||||||
REFRESH_ACCESS_TOKEN_ENDPOINT=''
|
REFRESH_ACCESS_TOKEN_ENDPOINT=''
|
||||||
SEARCH_CATALOG_URL=''
|
SEARCH_CATALOG_URL=''
|
||||||
SEGMENT_KEY=''
|
SEGMENT_KEY=''
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ ECOMMERCE_BASE_URL='http://localhost:18130'
|
|||||||
ENABLE_JUMPNAV='true'
|
ENABLE_JUMPNAV='true'
|
||||||
ENABLE_NOTICES=''
|
ENABLE_NOTICES=''
|
||||||
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734'
|
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734'
|
||||||
EXAMS_BASE_URL='http://localhost:18740'
|
|
||||||
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
||||||
IGNORED_ERROR_REGEX=''
|
IGNORED_ERROR_REGEX=''
|
||||||
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
|
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
|
||||||
@@ -29,8 +28,6 @@ LEGACY_THEME_NAME=''
|
|||||||
MARKETING_SITE_BASE_URL='http://localhost:18000'
|
MARKETING_SITE_BASE_URL='http://localhost:18000'
|
||||||
ORDER_HISTORY_URL='http://localhost:1996/orders'
|
ORDER_HISTORY_URL='http://localhost:1996/orders'
|
||||||
PORT=2000
|
PORT=2000
|
||||||
PROCTORED_EXAM_FAQ_URL=''
|
|
||||||
PROCTORED_EXAM_RULES_URL=''
|
|
||||||
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||||
SEARCH_CATALOG_URL='http://localhost:18000/courses'
|
SEARCH_CATALOG_URL='http://localhost:18000/courses'
|
||||||
SEGMENT_KEY=''
|
SEGMENT_KEY=''
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ ECOMMERCE_BASE_URL='http://localhost:18130'
|
|||||||
ENABLE_JUMPNAV='true'
|
ENABLE_JUMPNAV='true'
|
||||||
ENABLE_NOTICES=''
|
ENABLE_NOTICES=''
|
||||||
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734'
|
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734'
|
||||||
EXAMS_BASE_URL='http://localhost:18740'
|
|
||||||
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
||||||
IGNORED_ERROR_REGEX=''
|
IGNORED_ERROR_REGEX=''
|
||||||
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
|
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
|
||||||
@@ -29,8 +28,6 @@ LEGACY_THEME_NAME=''
|
|||||||
MARKETING_SITE_BASE_URL='http://localhost:18000'
|
MARKETING_SITE_BASE_URL='http://localhost:18000'
|
||||||
ORDER_HISTORY_URL='http://localhost:1996/orders'
|
ORDER_HISTORY_URL='http://localhost:1996/orders'
|
||||||
PORT=2000
|
PORT=2000
|
||||||
PROCTORED_EXAM_FAQ_URL=''
|
|
||||||
PROCTORED_EXAM_RULES_URL=''
|
|
||||||
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||||
SEARCH_CATALOG_URL='http://localhost:18000/courses'
|
SEARCH_CATALOG_URL='http://localhost:18000/courses'
|
||||||
SEGMENT_KEY=''
|
SEGMENT_KEY=''
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ jobs:
|
|||||||
secrets:
|
secrets:
|
||||||
GITHUB_APP_ID: ${{ secrets.GRAPHQL_AUTH_APP_ID }}
|
GITHUB_APP_ID: ${{ secrets.GRAPHQL_AUTH_APP_ID }}
|
||||||
GITHUB_APP_PRIVATE_KEY: ${{ secrets.GRAPHQL_AUTH_APP_PEM }}
|
GITHUB_APP_PRIVATE_KEY: ${{ secrets.GRAPHQL_AUTH_APP_PEM }}
|
||||||
SLACK_BOT_TOKEN: ${{ secrets.SLACK_ISSUE_BOT_TOKEN }}
|
SLACK_BOT_TOKEN: ${{ secrets.SLACK_ISSUE_BOT_TOKEN }}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# This workflow runs when a comment is made on the ticket
|
|
||||||
# If the comment starts with "label: " it tries to apply
|
|
||||||
# the label indicated in rest of comment.
|
|
||||||
# If the comment starts with "remove label: ", it tries
|
|
||||||
# to remove the indicated label.
|
|
||||||
# Note: Labels are allowed to have spaces and this script does
|
|
||||||
# not parse spaces (as often a space is legitimate), so the command
|
|
||||||
# "label: really long lots of words label" will apply the
|
|
||||||
# label "really long lots of words label"
|
|
||||||
|
|
||||||
name: Allows for the adding and removing of labels via comment
|
|
||||||
|
|
||||||
on:
|
|
||||||
issue_comment:
|
|
||||||
types: [created]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
add_remove_labels:
|
|
||||||
uses: openedx/.github/.github/workflows/add-remove-label-on-comment.yml@master
|
|
||||||
|
|
||||||
2
.github/workflows/lockfileversion-check.yml
vendored
2
.github/workflows/lockfileversion-check.yml
vendored
@@ -10,4 +10,4 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
version-check:
|
version-check:
|
||||||
uses: openedx/.github/.github/workflows/lockfileversion-check-v3.yml@master
|
uses: openedx/.github/.github/workflows/lockfileversion-check.yml@master
|
||||||
|
|||||||
12
.github/workflows/self-assign-issue.yml
vendored
12
.github/workflows/self-assign-issue.yml
vendored
@@ -1,12 +0,0 @@
|
|||||||
# This workflow runs when a comment is made on the ticket
|
|
||||||
# If the comment starts with "assign me" it assigns the author to the
|
|
||||||
# ticket (case insensitive)
|
|
||||||
|
|
||||||
name: Assign comment author to ticket if they say "assign me"
|
|
||||||
on:
|
|
||||||
issue_comment:
|
|
||||||
types: [created]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
self_assign_by_comment:
|
|
||||||
uses: openedx/.github/.github/workflows/self-assign-issue.yml@master
|
|
||||||
7
.github/workflows/validate.yml
vendored
7
.github/workflows/validate.yml
vendored
@@ -9,13 +9,14 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node: [16]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Setup Nodejs Env
|
|
||||||
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VER }}
|
node-version: ${{ matrix.node }}
|
||||||
- run: make validate.ci
|
- run: make validate.ci
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v3
|
||||||
|
|||||||
40837
package-lock.json
generated
40837
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
@@ -17,7 +17,8 @@
|
|||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"snapshot": "fedx-scripts jest --updateSnapshot",
|
"snapshot": "fedx-scripts jest --updateSnapshot",
|
||||||
"start": "fedx-scripts webpack-dev-server --progress",
|
"start": "fedx-scripts webpack-dev-server --progress",
|
||||||
"test": "fedx-scripts jest --coverage --passWithNoTests"
|
"test": "fedx-scripts jest --coverage --passWithNoTests",
|
||||||
|
"testx": "fedx-scripts jest -t 'Outline Tab' --coverage --passWithNoTests"
|
||||||
},
|
},
|
||||||
"author": "edX",
|
"author": "edX",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
@@ -29,12 +30,12 @@
|
|||||||
"url": "https://github.com/openedx/frontend-app-learning/issues"
|
"url": "https://github.com/openedx/frontend-app-learning/issues"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@edx/brand": "npm:@edx/brand-openedx@1.2.0",
|
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
|
||||||
"@edx/frontend-component-footer": "^12.0.0",
|
"@edx/frontend-component-footer": "11.1.0",
|
||||||
"@edx/frontend-component-header": "^4.0.0",
|
"@edx/frontend-component-header": "^3.6.0",
|
||||||
"@edx/frontend-lib-special-exams": "^2.16.1",
|
"@edx/frontend-lib-special-exams": "^2.2.0",
|
||||||
"@edx/frontend-platform": "^4.2.0",
|
"@edx/frontend-platform": "2.5.1",
|
||||||
"@edx/paragon": "20.28.4",
|
"@edx/paragon": "^20.24.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "1.3.0",
|
"@fortawesome/fontawesome-svg-core": "1.3.0",
|
||||||
"@fortawesome/free-brands-svg-icons": "5.15.4",
|
"@fortawesome/free-brands-svg-icons": "5.15.4",
|
||||||
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||||
@@ -44,18 +45,18 @@
|
|||||||
"@reduxjs/toolkit": "1.8.1",
|
"@reduxjs/toolkit": "1.8.1",
|
||||||
"classnames": "2.3.2",
|
"classnames": "2.3.2",
|
||||||
"core-js": "3.22.2",
|
"core-js": "3.22.2",
|
||||||
"history": "5.3.0",
|
"history": "^5.3.0",
|
||||||
"js-cookie": "3.0.1",
|
"js-cookie": "3.0.1",
|
||||||
"lodash.camelcase": "4.3.0",
|
"lodash.camelcase": "4.3.0",
|
||||||
"prop-types": "15.8.1",
|
"prop-types": "15.8.1",
|
||||||
"query-string": "7.1.3",
|
"query-string": "^7.1.1",
|
||||||
"react": "16.14.0",
|
"react": "16.14.0",
|
||||||
"react-dom": "16.14.0",
|
"react-dom": "16.14.0",
|
||||||
"react-helmet": "6.1.0",
|
"react-helmet": "6.1.0",
|
||||||
"react-redux": "7.2.9",
|
"react-redux": "7.2.9",
|
||||||
"react-router": "5.2.1",
|
"react-router": "5.2.1",
|
||||||
"react-router-dom": "5.3.0",
|
"react-router-dom": "5.3.0",
|
||||||
"react-share": "4.4.1",
|
"react-share": "^4.4.1",
|
||||||
"redux": "4.1.2",
|
"redux": "4.1.2",
|
||||||
"regenerator-runtime": "0.13.11",
|
"regenerator-runtime": "0.13.11",
|
||||||
"reselect": "4.1.7",
|
"reselect": "4.1.7",
|
||||||
@@ -64,7 +65,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@edx/browserslist-config": "1.1.1",
|
"@edx/browserslist-config": "1.1.1",
|
||||||
"@edx/frontend-build": "^12.8.27",
|
"@edx/frontend-build": "^12.4.15",
|
||||||
"@edx/reactifex": "2.1.1",
|
"@edx/reactifex": "2.1.1",
|
||||||
"@pact-foundation/pact": "9.17.3",
|
"@pact-foundation/pact": "9.17.3",
|
||||||
"@testing-library/jest-dom": "5.16.5",
|
"@testing-library/jest-dom": "5.16.5",
|
||||||
@@ -74,7 +75,7 @@
|
|||||||
"copy-webpack-plugin": "^11.0.0",
|
"copy-webpack-plugin": "^11.0.0",
|
||||||
"es-check": "6.2.1",
|
"es-check": "6.2.1",
|
||||||
"husky": "7.0.4",
|
"husky": "7.0.4",
|
||||||
"jest": "29.5.0",
|
"jest": "27.5.1",
|
||||||
"rosie": "2.1.0"
|
"rosie": "2.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,186 +39,183 @@ describe('Course Home Service', () => {
|
|||||||
afterAll(() => provider.finalize());
|
afterAll(() => provider.finalize());
|
||||||
describe('When a request to fetch tab is made', () => {
|
describe('When a request to fetch tab is made', () => {
|
||||||
it('returns tab data for a course_id', async () => {
|
it('returns tab data for a course_id', async () => {
|
||||||
setTimeout(() => {
|
await provider.addInteraction({
|
||||||
provider.addInteraction({
|
state: `Tab data exists for course_id ${courseId}`,
|
||||||
state: `Tab data exists for course_id ${courseId}`,
|
uponReceiving: 'a request to fetch tab',
|
||||||
uponReceiving: 'a request to fetch tab',
|
withRequest: {
|
||||||
withRequest: {
|
method: 'GET',
|
||||||
method: 'GET',
|
path: `/api/course_home/course_metadata/${courseId}`,
|
||||||
path: `/api/course_home/course_metadata/${courseId}`,
|
},
|
||||||
},
|
willRespondWith: {
|
||||||
willRespondWith: {
|
status: 200,
|
||||||
status: 200,
|
body: {
|
||||||
body: {
|
can_show_upgrade_sock: boolean(false),
|
||||||
can_show_upgrade_sock: boolean(false),
|
verified_mode: like({
|
||||||
verified_mode: like({
|
access_expiration_date: null,
|
||||||
access_expiration_date: null,
|
currency: 'USD',
|
||||||
currency: 'USD',
|
currency_symbol: '$',
|
||||||
currency_symbol: '$',
|
price: 149,
|
||||||
price: 149,
|
sku: '8CF08E5',
|
||||||
sku: '8CF08E5',
|
upgrade_url: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
|
||||||
upgrade_url: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
|
}),
|
||||||
}),
|
celebrations: like({
|
||||||
celebrations: like({
|
first_section: false,
|
||||||
first_section: false,
|
streak_length_to_celebrate: null,
|
||||||
streak_length_to_celebrate: null,
|
streak_discount_enabled: false,
|
||||||
streak_discount_enabled: false,
|
}),
|
||||||
}),
|
course_access: {
|
||||||
course_access: {
|
has_access: boolean(true),
|
||||||
has_access: boolean(true),
|
error_code: null,
|
||||||
error_code: null,
|
developer_message: null,
|
||||||
developer_message: null,
|
user_message: null,
|
||||||
user_message: null,
|
additional_context_user_message: null,
|
||||||
additional_context_user_message: null,
|
user_fragment: null,
|
||||||
user_fragment: null,
|
|
||||||
},
|
|
||||||
course_id: term({
|
|
||||||
generate: 'course-v1:edX+DemoX+Demo_Course',
|
|
||||||
matcher: opaqueKeysRegex,
|
|
||||||
}),
|
|
||||||
is_enrolled: boolean(true),
|
|
||||||
is_self_paced: boolean(false),
|
|
||||||
is_staff: boolean(true),
|
|
||||||
number: string('DemoX'),
|
|
||||||
org: string('edX'),
|
|
||||||
original_user_is_staff: boolean(true),
|
|
||||||
start: term({
|
|
||||||
generate: '2013-02-05T05:00:00Z',
|
|
||||||
matcher: dateRegex,
|
|
||||||
}),
|
|
||||||
tabs: eachLike({
|
|
||||||
tab_id: 'courseware',
|
|
||||||
title: 'Course',
|
|
||||||
url: `${getConfig().BASE_URL}/course/course-v1:edX+DemoX+Demo_Course/home`,
|
|
||||||
}),
|
|
||||||
title: string('Demonstration Course'),
|
|
||||||
username: string('edx'),
|
|
||||||
},
|
},
|
||||||
},
|
course_id: term({
|
||||||
});
|
generate: 'course-v1:edX+DemoX+Demo_Course',
|
||||||
const normalizedTabData = {
|
matcher: opaqueKeysRegex,
|
||||||
canShowUpgradeSock: false,
|
}),
|
||||||
verifiedMode: {
|
is_enrolled: boolean(true),
|
||||||
accessExpirationDate: null,
|
is_self_paced: boolean(false),
|
||||||
currency: 'USD',
|
is_staff: boolean(true),
|
||||||
currencySymbol: '$',
|
number: string('DemoX'),
|
||||||
price: 149,
|
org: string('edX'),
|
||||||
sku: '8CF08E5',
|
original_user_is_staff: boolean(true),
|
||||||
upgradeUrl: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
|
start: term({
|
||||||
},
|
generate: '2013-02-05T05:00:00Z',
|
||||||
celebrations: {
|
matcher: dateRegex,
|
||||||
firstSection: false,
|
}),
|
||||||
streakLengthToCelebrate: null,
|
tabs: eachLike({
|
||||||
streakDiscountEnabled: false,
|
tab_id: 'courseware',
|
||||||
},
|
|
||||||
courseAccess: {
|
|
||||||
hasAccess: true,
|
|
||||||
errorCode: null,
|
|
||||||
developerMessage: null,
|
|
||||||
userMessage: null,
|
|
||||||
additionalContextUserMessage: null,
|
|
||||||
userFragment: null,
|
|
||||||
},
|
|
||||||
courseId: 'course-v1:edX+DemoX+Demo_Course',
|
|
||||||
isEnrolled: true,
|
|
||||||
isMasquerading: false,
|
|
||||||
isSelfPaced: false,
|
|
||||||
isStaff: true,
|
|
||||||
number: 'DemoX',
|
|
||||||
org: 'edX',
|
|
||||||
originalUserIsStaff: true,
|
|
||||||
start: '2013-02-05T05:00:00Z',
|
|
||||||
tabs: [
|
|
||||||
{
|
|
||||||
slug: 'outline',
|
|
||||||
title: 'Course',
|
title: 'Course',
|
||||||
url: `${getConfig().BASE_URL}/course/course-v1:edX+DemoX+Demo_Course/home`,
|
url: `${getConfig().BASE_URL}/course/course-v1:edX+DemoX+Demo_Course/home`,
|
||||||
},
|
}),
|
||||||
],
|
title: string('Demonstration Course'),
|
||||||
title: 'Demonstration Course',
|
username: string('edx'),
|
||||||
username: 'edx',
|
},
|
||||||
};
|
},
|
||||||
const response = getCourseHomeCourseMetadata(courseId, 'outline');
|
});
|
||||||
expect(response).toBeTruthy();
|
const normalizedTabData = {
|
||||||
expect(response).toEqual(normalizedTabData);
|
canShowUpgradeSock: false,
|
||||||
}, 100);
|
verifiedMode: {
|
||||||
|
accessExpirationDate: null,
|
||||||
|
currency: 'USD',
|
||||||
|
currencySymbol: '$',
|
||||||
|
price: 149,
|
||||||
|
sku: '8CF08E5',
|
||||||
|
upgradeUrl: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
|
||||||
|
},
|
||||||
|
celebrations: {
|
||||||
|
firstSection: false,
|
||||||
|
streakLengthToCelebrate: null,
|
||||||
|
streakDiscountEnabled: false,
|
||||||
|
},
|
||||||
|
courseAccess: {
|
||||||
|
hasAccess: true,
|
||||||
|
errorCode: null,
|
||||||
|
developerMessage: null,
|
||||||
|
userMessage: null,
|
||||||
|
additionalContextUserMessage: null,
|
||||||
|
userFragment: null,
|
||||||
|
},
|
||||||
|
courseId: 'course-v1:edX+DemoX+Demo_Course',
|
||||||
|
isEnrolled: true,
|
||||||
|
isMasquerading: false,
|
||||||
|
isSelfPaced: false,
|
||||||
|
isStaff: true,
|
||||||
|
number: 'DemoX',
|
||||||
|
org: 'edX',
|
||||||
|
originalUserIsStaff: true,
|
||||||
|
start: '2013-02-05T05:00:00Z',
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
slug: 'outline',
|
||||||
|
title: 'Course',
|
||||||
|
url: `${getConfig().BASE_URL}/course/course-v1:edX+DemoX+Demo_Course/home`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
title: 'Demonstration Course',
|
||||||
|
username: 'edx',
|
||||||
|
};
|
||||||
|
const response = await getCourseHomeCourseMetadata(courseId, 'outline');
|
||||||
|
expect(response).toBeTruthy();
|
||||||
|
expect(response).toEqual(normalizedTabData);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When a request to fetch dates tab is made', () => {
|
describe('When a request to fetch dates tab is made', () => {
|
||||||
it('returns course date blocks for a course_id', async () => {
|
it('returns course date blocks for a course_id', async () => {
|
||||||
setTimeout(() => {
|
await provider.addInteraction({
|
||||||
provider.addInteraction({
|
state: `course date blocks exist for course_id ${courseId}`,
|
||||||
state: `course date blocks exist for course_id ${courseId}`,
|
uponReceiving: 'a request to fetch dates tab',
|
||||||
uponReceiving: 'a request to fetch dates tab',
|
withRequest: {
|
||||||
withRequest: {
|
method: 'GET',
|
||||||
method: 'GET',
|
path: `/api/course_home/dates/${courseId}`,
|
||||||
path: `/api/course_home/dates/${courseId}`,
|
},
|
||||||
},
|
willRespondWith: {
|
||||||
willRespondWith: {
|
status: 200,
|
||||||
status: 200,
|
body: {
|
||||||
body: {
|
dates_banner_info: like({
|
||||||
dates_banner_info: like({
|
missed_deadlines: false,
|
||||||
missed_deadlines: false,
|
content_type_gating_enabled: false,
|
||||||
content_type_gating_enabled: false,
|
missed_gated_content: false,
|
||||||
missed_gated_content: false,
|
verified_upgrade_link: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
|
||||||
verified_upgrade_link: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
|
}),
|
||||||
}),
|
course_date_blocks: eachLike({
|
||||||
course_date_blocks: eachLike({
|
assignment_type: null,
|
||||||
assignment_type: null,
|
|
||||||
complete: null,
|
|
||||||
date: term({
|
|
||||||
generate: '2013-02-05T05:00:00Z',
|
|
||||||
matcher: dateRegex,
|
|
||||||
}),
|
|
||||||
date_type: term({
|
|
||||||
generate: 'verified-upgrade-deadline',
|
|
||||||
matcher: dateTypeRegex,
|
|
||||||
}),
|
|
||||||
description: 'You are still eligible to upgrade to a Verified Certificate! Pursue it to highlight the knowledge and skills you gain in this course.',
|
|
||||||
learner_has_access: true,
|
|
||||||
link: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
|
|
||||||
link_text: 'Upgrade to Verified Certificate',
|
|
||||||
title: 'Verification Upgrade Deadline',
|
|
||||||
extra_info: null,
|
|
||||||
first_component_block_id: '',
|
|
||||||
}),
|
|
||||||
has_ended: boolean(false),
|
|
||||||
learner_is_full_access: boolean(true),
|
|
||||||
user_timezone: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const camelCaseResponse = {
|
|
||||||
datesBannerInfo: {
|
|
||||||
missedDeadlines: false,
|
|
||||||
contentTypeGatingEnabled: false,
|
|
||||||
missedGatedContent: false,
|
|
||||||
verifiedUpgradeLink: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
|
|
||||||
},
|
|
||||||
courseDateBlocks: [
|
|
||||||
{
|
|
||||||
assignmentType: null,
|
|
||||||
complete: null,
|
complete: null,
|
||||||
date: '2013-02-05T05:00:00Z',
|
date: term({
|
||||||
dateType: 'verified-upgrade-deadline',
|
generate: '2013-02-05T05:00:00Z',
|
||||||
|
matcher: dateRegex,
|
||||||
|
}),
|
||||||
|
date_type: term({
|
||||||
|
generate: 'verified-upgrade-deadline',
|
||||||
|
matcher: dateTypeRegex,
|
||||||
|
}),
|
||||||
description: 'You are still eligible to upgrade to a Verified Certificate! Pursue it to highlight the knowledge and skills you gain in this course.',
|
description: 'You are still eligible to upgrade to a Verified Certificate! Pursue it to highlight the knowledge and skills you gain in this course.',
|
||||||
learnerHasAccess: true,
|
learner_has_access: true,
|
||||||
link: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
|
link: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
|
||||||
linkText: 'Upgrade to Verified Certificate',
|
link_text: 'Upgrade to Verified Certificate',
|
||||||
title: 'Verification Upgrade Deadline',
|
title: 'Verification Upgrade Deadline',
|
||||||
extraInfo: null,
|
extra_info: null,
|
||||||
firstComponentBlockId: '',
|
first_component_block_id: '',
|
||||||
},
|
}),
|
||||||
],
|
has_ended: boolean(false),
|
||||||
hasEnded: false,
|
learner_is_full_access: boolean(true),
|
||||||
learnerIsFullAccess: true,
|
user_timezone: null,
|
||||||
userTimezone: null,
|
},
|
||||||
};
|
},
|
||||||
const response = getDatesTabData(courseId);
|
});
|
||||||
expect(response).toBeTruthy();
|
const camelCaseResponse = {
|
||||||
expect(response).toEqual(camelCaseResponse);
|
datesBannerInfo: {
|
||||||
}, 100);
|
missedDeadlines: false,
|
||||||
|
contentTypeGatingEnabled: false,
|
||||||
|
missedGatedContent: false,
|
||||||
|
verifiedUpgradeLink: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
|
||||||
|
},
|
||||||
|
courseDateBlocks: [
|
||||||
|
{
|
||||||
|
assignmentType: null,
|
||||||
|
complete: null,
|
||||||
|
date: '2013-02-05T05:00:00Z',
|
||||||
|
dateType: 'verified-upgrade-deadline',
|
||||||
|
description: 'You are still eligible to upgrade to a Verified Certificate! Pursue it to highlight the knowledge and skills you gain in this course.',
|
||||||
|
learnerHasAccess: true,
|
||||||
|
link: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
|
||||||
|
linkText: 'Upgrade to Verified Certificate',
|
||||||
|
title: 'Verification Upgrade Deadline',
|
||||||
|
extraInfo: null,
|
||||||
|
firstComponentBlockId: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
hasEnded: false,
|
||||||
|
learnerIsFullAccess: true,
|
||||||
|
userTimezone: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await getDatesTabData(courseId);
|
||||||
|
expect(response).toBeTruthy();
|
||||||
|
expect(response).toEqual(camelCaseResponse);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import { CERT_STATUS_TYPE } from './alerts/certificate-status-alert/CertificateS
|
|||||||
import OutlineTab from './OutlineTab';
|
import OutlineTab from './OutlineTab';
|
||||||
import LoadedTabPage from '../../tab-page/LoadedTabPage';
|
import LoadedTabPage from '../../tab-page/LoadedTabPage';
|
||||||
|
|
||||||
|
import SequenceLink from './SequenceLink';
|
||||||
|
|
||||||
initializeMockApp();
|
initializeMockApp();
|
||||||
jest.mock('@edx/frontend-platform/analytics');
|
jest.mock('@edx/frontend-platform/analytics');
|
||||||
|
|
||||||
@@ -119,11 +121,11 @@ describe('Outline Tab', () => {
|
|||||||
|
|
||||||
// Click to expand section
|
// Click to expand section
|
||||||
userEvent.click(expandButton);
|
userEvent.click(expandButton);
|
||||||
await waitFor(() => expect(collapsedSectionNode).toHaveAttribute('aria-expanded', 'true'));
|
expect(collapsedSectionNode).toHaveAttribute('aria-expanded', 'true');
|
||||||
|
|
||||||
// Click to collapse section
|
// Click to collapse section
|
||||||
userEvent.click(expandButton);
|
userEvent.click(expandButton);
|
||||||
await waitFor(() => expect(collapsedSectionNode).toHaveAttribute('aria-expanded', 'false'));
|
expect(collapsedSectionNode).toHaveAttribute('aria-expanded', 'false');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('displays correct icon for complete assignment', async () => {
|
it('displays correct icon for complete assignment', async () => {
|
||||||
@@ -154,6 +156,49 @@ describe('Outline Tab', () => {
|
|||||||
const sequenceLink = screen.getByText('Title of Sequence');
|
const sequenceLink = screen.getByText('Title of Sequence');
|
||||||
expect(sequenceLink.getAttribute('href')).toContain(`/course/${courseId}`);
|
expect(sequenceLink.getAttribute('href')).toContain(`/course/${courseId}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('if exam due date set, exam description AND due date appear', async () => {
|
||||||
|
// Create a due date set a year into the future
|
||||||
|
const now = new Date();
|
||||||
|
const dueDate = new Date(now.getFullYear() + 1, now.getMonth(), now.getDate());
|
||||||
|
|
||||||
|
// Build course blocks with a future due date set
|
||||||
|
const { courseBlocks } = await buildMinimalCourseBlocks(courseId, 'Title', {
|
||||||
|
sequenceBlocks: [
|
||||||
|
<SequenceLink
|
||||||
|
key={0}
|
||||||
|
id={0}
|
||||||
|
courseId={courseId}
|
||||||
|
sequence={
|
||||||
|
{
|
||||||
|
complete: false,
|
||||||
|
description: 'Description of Sequence',
|
||||||
|
due: dueDate,
|
||||||
|
showLink: true,
|
||||||
|
title: 'Title of Subsection',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
first={null}
|
||||||
|
/>,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
setTabData({
|
||||||
|
course_blocks: { blocks: courseBlocks.blocks },
|
||||||
|
});
|
||||||
|
await fetchAndRender();
|
||||||
|
|
||||||
|
// Button renders as "Expand All"
|
||||||
|
const expandButton = screen.getByRole('button', { name: 'Expand all' });
|
||||||
|
expect(expandButton).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Click to expand section
|
||||||
|
userEvent.click(expandButton);
|
||||||
|
|
||||||
|
// Look for a substring that says "(exam type) Exam due (datetime)""
|
||||||
|
expect(screen.getByText(/Exam due/)).toBeInDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
// If due date is NOT set, ONLY display description
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Suggested schedule alerts', () => {
|
describe('Suggested schedule alerts', () => {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import MockAdapter from 'axios-mock-adapter';
|
|||||||
|
|
||||||
import { UserMessagesProvider } from '../generic/user-messages';
|
import { UserMessagesProvider } from '../generic/user-messages';
|
||||||
import tabMessages from '../tab-page/messages';
|
import tabMessages from '../tab-page/messages';
|
||||||
import { initializeMockApp, waitFor } from '../setupTest';
|
import { initializeMockApp } from '../setupTest';
|
||||||
|
|
||||||
import CoursewareContainer from './CoursewareContainer';
|
import CoursewareContainer from './CoursewareContainer';
|
||||||
import { buildSimpleCourseBlocks, buildBinaryCourseBlocks } from '../shared/data/__factories__/courseBlocks.factory';
|
import { buildSimpleCourseBlocks, buildBinaryCourseBlocks } from '../shared/data/__factories__/courseBlocks.factory';
|
||||||
@@ -211,7 +211,7 @@ describe('CoursewareContainer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
history.push(`/course/${courseId}`);
|
history.push(`/course/${courseId}`);
|
||||||
const container = await waitFor(() => loadContainer());
|
const container = await loadContainer();
|
||||||
|
|
||||||
assertLoadedHeader(container);
|
assertLoadedHeader(container);
|
||||||
assertSequenceNavigation(container);
|
assertSequenceNavigation(container);
|
||||||
@@ -234,7 +234,7 @@ describe('CoursewareContainer', () => {
|
|||||||
axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/courseware/resume/${courseId}`).reply(200, {});
|
axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/courseware/resume/${courseId}`).reply(200, {});
|
||||||
|
|
||||||
history.push(`/course/${courseId}`);
|
history.push(`/course/${courseId}`);
|
||||||
const container = await waitFor(() => loadContainer());
|
const container = await loadContainer();
|
||||||
|
|
||||||
assertLoadedHeader(container);
|
assertLoadedHeader(container);
|
||||||
assertSequenceNavigation(container);
|
assertSequenceNavigation(container);
|
||||||
@@ -284,7 +284,7 @@ describe('CoursewareContainer', () => {
|
|||||||
describe('when the URL does not contain a unit ID', () => {
|
describe('when the URL does not contain a unit ID', () => {
|
||||||
it('should choose a unit within the section\'s first sequence', async () => {
|
it('should choose a unit within the section\'s first sequence', async () => {
|
||||||
setUrl(sectionTree[1].id);
|
setUrl(sectionTree[1].id);
|
||||||
const container = await waitFor(() => loadContainer());
|
const container = await loadContainer();
|
||||||
assertLoadedHeader(container);
|
assertLoadedHeader(container);
|
||||||
assertSequenceNavigation(container, 2);
|
assertSequenceNavigation(container, 2);
|
||||||
assertLocation(container, sequenceTree[1][0].id, unitTree[1][0][0].id);
|
assertLocation(container, sequenceTree[1][0].id, unitTree[1][0][0].id);
|
||||||
@@ -359,7 +359,7 @@ describe('CoursewareContainer', () => {
|
|||||||
|
|
||||||
it('should pick the first unit if position was not defined (activeUnitIndex becomes 0)', async () => {
|
it('should pick the first unit if position was not defined (activeUnitIndex becomes 0)', async () => {
|
||||||
history.push(`/course/${courseId}/${sequenceBlock.id}`);
|
history.push(`/course/${courseId}/${sequenceBlock.id}`);
|
||||||
const container = await waitFor(() => loadContainer());
|
const container = await loadContainer();
|
||||||
|
|
||||||
assertLoadedHeader(container);
|
assertLoadedHeader(container);
|
||||||
assertSequenceNavigation(container);
|
assertSequenceNavigation(container);
|
||||||
@@ -378,7 +378,7 @@ describe('CoursewareContainer', () => {
|
|||||||
setUpMockRequests({ sequenceMetadatas: [sequenceMetadata] });
|
setUpMockRequests({ sequenceMetadatas: [sequenceMetadata] });
|
||||||
|
|
||||||
history.push(`/course/${courseId}/${sequenceBlock.id}`);
|
history.push(`/course/${courseId}/${sequenceBlock.id}`);
|
||||||
const container = await waitFor(() => loadContainer());
|
const container = await loadContainer();
|
||||||
|
|
||||||
assertLoadedHeader(container);
|
assertLoadedHeader(container);
|
||||||
assertSequenceNavigation(container);
|
assertSequenceNavigation(container);
|
||||||
@@ -395,7 +395,7 @@ describe('CoursewareContainer', () => {
|
|||||||
|
|
||||||
it('should load the specified unit', async () => {
|
it('should load the specified unit', async () => {
|
||||||
history.push(`/course/${courseId}/${sequenceBlock.id}/${unitBlocks[2].id}`);
|
history.push(`/course/${courseId}/${sequenceBlock.id}/${unitBlocks[2].id}`);
|
||||||
const container = await waitFor(() => loadContainer());
|
const container = await loadContainer();
|
||||||
|
|
||||||
assertLoadedHeader(container);
|
assertLoadedHeader(container);
|
||||||
assertSequenceNavigation(container);
|
assertSequenceNavigation(container);
|
||||||
@@ -411,7 +411,7 @@ describe('CoursewareContainer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
history.push(`/course/${courseId}/${sequenceBlock.id}/${unitBlocks[0].id}`);
|
history.push(`/course/${courseId}/${sequenceBlock.id}/${unitBlocks[0].id}`);
|
||||||
const container = await waitFor(() => loadContainer());
|
const container = await loadContainer();
|
||||||
|
|
||||||
const sequenceNavButtons = container.querySelectorAll('nav.sequence-navigation button');
|
const sequenceNavButtons = container.querySelectorAll('nav.sequence-navigation button');
|
||||||
const sequenceNextButton = sequenceNavButtons[4];
|
const sequenceNextButton = sequenceNavButtons[4];
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import { PageRoute } from '@edx/frontend-platform/react';
|
|||||||
import queryString from 'query-string';
|
import queryString from 'query-string';
|
||||||
import PageLoading from '../generic/PageLoading';
|
import PageLoading from '../generic/PageLoading';
|
||||||
|
|
||||||
import DecodePageRoute from '../decode-page-route';
|
|
||||||
|
|
||||||
const CoursewareRedirectLandingPage = () => {
|
const CoursewareRedirectLandingPage = () => {
|
||||||
const { path } = useRouteMatch();
|
const { path } = useRouteMatch();
|
||||||
return (
|
return (
|
||||||
@@ -23,7 +21,7 @@ const CoursewareRedirectLandingPage = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Switch>
|
<Switch>
|
||||||
<DecodePageRoute
|
<PageRoute
|
||||||
path={`${path}/survey/:courseId`}
|
path={`${path}/survey/:courseId`}
|
||||||
render={({ match }) => {
|
render={({ match }) => {
|
||||||
global.location.assign(`${getConfig().LMS_BASE_URL}/courses/${match.params.courseId}/survey`);
|
global.location.assign(`${getConfig().LMS_BASE_URL}/courses/${match.params.courseId}/survey`);
|
||||||
@@ -42,7 +40,7 @@ const CoursewareRedirectLandingPage = () => {
|
|||||||
global.location.assign(`${getConfig().LMS_BASE_URL}${consentPath}`);
|
global.location.assign(`${getConfig().LMS_BASE_URL}${consentPath}`);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<DecodePageRoute
|
<PageRoute
|
||||||
path={`${path}/home/:courseId`}
|
path={`${path}/home/:courseId`}
|
||||||
render={({ match }) => {
|
render={({ match }) => {
|
||||||
global.location.assign(`/course/${match.params.courseId}/home`);
|
global.location.assign(`/course/${match.params.courseId}/home`);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
@@ -43,8 +43,10 @@ const Course = ({
|
|||||||
|
|
||||||
// Below the tabs, above the breadcrumbs alerts (appearing in the order listed here)
|
// Below the tabs, above the breadcrumbs alerts (appearing in the order listed here)
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const celebrateFirstSection = celebrations && celebrations.firstSection;
|
||||||
const [firstSectionCelebrationOpen, setFirstSectionCelebrationOpen] = useState(false);
|
const [firstSectionCelebrationOpen, setFirstSectionCelebrationOpen] = useState(
|
||||||
|
shouldCelebrateOnSectionLoad(courseId, sequenceId, celebrateFirstSection, dispatch, celebrations),
|
||||||
|
);
|
||||||
// If streakLengthToCelebrate is populated, that modal takes precedence. Wait til the next load to display
|
// If streakLengthToCelebrate is populated, that modal takes precedence. Wait til the next load to display
|
||||||
// the weekly goal celebration modal.
|
// the weekly goal celebration modal.
|
||||||
const [weeklyGoalCelebrationOpen, setWeeklyGoalCelebrationOpen] = useState(
|
const [weeklyGoalCelebrationOpen, setWeeklyGoalCelebrationOpen] = useState(
|
||||||
@@ -66,17 +68,6 @@ const Course = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const celebrateFirstSection = celebrations && celebrations.firstSection;
|
|
||||||
setFirstSectionCelebrationOpen(shouldCelebrateOnSectionLoad(
|
|
||||||
courseId,
|
|
||||||
sequenceId,
|
|
||||||
celebrateFirstSection,
|
|
||||||
dispatch,
|
|
||||||
celebrations,
|
|
||||||
));
|
|
||||||
}, [sequenceId]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarProvider courseId={courseId} unitId={unitId}>
|
<SidebarProvider courseId={courseId} unitId={unitId}>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Factory } from 'rosie';
|
import { Factory } from 'rosie';
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
|
||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
|
||||||
import MockAdapter from 'axios-mock-adapter';
|
|
||||||
import { breakpoints } from '@edx/paragon';
|
import { breakpoints } from '@edx/paragon';
|
||||||
import {
|
import {
|
||||||
act, fireEvent, getByRole, initializeTestStore, loadUnit, render, screen, waitFor,
|
fireEvent, getByRole, initializeTestStore, loadUnit, render, screen, waitFor,
|
||||||
} from '../../setupTest';
|
} from '../../setupTest';
|
||||||
import { buildTopicsFromUnits } from '../data/__factories__/discussionTopics.factory';
|
|
||||||
import { handleNextSectionCelebration } from './celebration';
|
import { handleNextSectionCelebration } from './celebration';
|
||||||
import * as celebrationUtils from './celebration/utils';
|
import * as celebrationUtils from './celebration/utils';
|
||||||
import Course from './Course';
|
import Course from './Course';
|
||||||
import { executeThunk } from '../../utils';
|
|
||||||
import * as thunks from '../data/thunks';
|
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform/analytics');
|
jest.mock('@edx/frontend-platform/analytics');
|
||||||
|
|
||||||
@@ -49,28 +43,6 @@ describe('Course', () => {
|
|||||||
setItemSpy.mockRestore();
|
setItemSpy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
const setupDiscussionSidebar = async (storageValue = false) => {
|
|
||||||
localStorage.clear();
|
|
||||||
const testStore = await initializeTestStore({ provider: 'openedx' });
|
|
||||||
const state = testStore.getState();
|
|
||||||
const { courseware: { courseId } } = state;
|
|
||||||
const axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
|
||||||
axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/discussion/v1/courses/${courseId}`).reply(200, { provider: 'openedx' });
|
|
||||||
const topicsResponse = buildTopicsFromUnits(state.models.units);
|
|
||||||
axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/discussion/v2/course_topics/${courseId}`)
|
|
||||||
.reply(200, topicsResponse);
|
|
||||||
|
|
||||||
await executeThunk(thunks.getCourseDiscussionTopics(courseId), testStore.dispatch);
|
|
||||||
const [firstUnitId] = Object.keys(state.models.units);
|
|
||||||
mockData.unitId = firstUnitId;
|
|
||||||
const [firstSequenceId] = Object.keys(state.models.sequences);
|
|
||||||
mockData.sequenceId = firstSequenceId;
|
|
||||||
if (storageValue !== null) {
|
|
||||||
localStorage.setItem('showDiscussionSidebar', storageValue);
|
|
||||||
}
|
|
||||||
await render(<Course {...mockData} />, { store: testStore });
|
|
||||||
};
|
|
||||||
|
|
||||||
it('loads learning sequence', async () => {
|
it('loads learning sequence', async () => {
|
||||||
render(<Course {...mockData} />);
|
render(<Course {...mockData} />);
|
||||||
expect(screen.getByRole('navigation', { name: 'breadcrumb' })).toBeInTheDocument();
|
expect(screen.getByRole('navigation', { name: 'breadcrumb' })).toBeInTheDocument();
|
||||||
@@ -131,7 +103,6 @@ describe('Course', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('displays notification trigger and toggles active class on click', async () => {
|
it('displays notification trigger and toggles active class on click', async () => {
|
||||||
localStorage.setItem('showDiscussionSidebar', false);
|
|
||||||
render(<Course {...mockData} />);
|
render(<Course {...mockData} />);
|
||||||
|
|
||||||
const notificationTrigger = screen.getByRole('button', { name: /Show notification tray/i });
|
const notificationTrigger = screen.getByRole('button', { name: /Show notification tray/i });
|
||||||
@@ -143,14 +114,13 @@ describe('Course', () => {
|
|||||||
|
|
||||||
it('handles click to open/close notification tray', async () => {
|
it('handles click to open/close notification tray', async () => {
|
||||||
sessionStorage.clear();
|
sessionStorage.clear();
|
||||||
localStorage.setItem('showDiscussionSidebar', false);
|
|
||||||
render(<Course {...mockData} />);
|
render(<Course {...mockData} />);
|
||||||
expect(sessionStorage.getItem(`notificationTrayStatus.${mockData.courseId}`)).toBe('"open"');
|
expect(sessionStorage.getItem(`notificationTrayStatus.${mockData.courseId}`)).toBe('"open"');
|
||||||
const notificationShowButton = await screen.findByRole('button', { name: /Show notification tray/i });
|
const notificationShowButton = await screen.findByRole('button', { name: /Show notification tray/i });
|
||||||
expect(screen.queryByRole('region', { name: /notification tray/i })).not.toHaveClass('d-none');
|
expect(screen.queryByRole('region', { name: /notification tray/i })).toBeInTheDocument();
|
||||||
fireEvent.click(notificationShowButton);
|
fireEvent.click(notificationShowButton);
|
||||||
expect(sessionStorage.getItem(`notificationTrayStatus.${mockData.courseId}`)).toBe('"closed"');
|
expect(sessionStorage.getItem(`notificationTrayStatus.${mockData.courseId}`)).toBe('"closed"');
|
||||||
expect(screen.queryByRole('region', { name: /notification tray/i })).toHaveClass('d-none');
|
expect(screen.queryByRole('region', { name: /notification tray/i })).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles reload persisting notification tray status', async () => {
|
it('handles reload persisting notification tray status', async () => {
|
||||||
@@ -174,7 +144,6 @@ describe('Course', () => {
|
|||||||
|
|
||||||
it('handles sessionStorage from a different course for the notification tray', async () => {
|
it('handles sessionStorage from a different course for the notification tray', async () => {
|
||||||
sessionStorage.clear();
|
sessionStorage.clear();
|
||||||
localStorage.setItem('showDiscussionSidebar', false);
|
|
||||||
const courseMetadataSecondCourse = Factory.build('courseMetadata', { id: 'second_course' });
|
const courseMetadataSecondCourse = Factory.build('courseMetadata', { id: 'second_course' });
|
||||||
|
|
||||||
// set sessionStorage for a different course before rendering Course
|
// set sessionStorage for a different course before rendering Course
|
||||||
@@ -217,34 +186,6 @@ describe('Course', () => {
|
|||||||
expect(screen.getByText(Object.values(models.sequences)[0].title)).toBeInTheDocument();
|
expect(screen.getByText(Object.values(models.sequences)[0].title)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
[
|
|
||||||
{ value: true, visible: true },
|
|
||||||
{ value: false, visible: false },
|
|
||||||
{ value: null, visible: true },
|
|
||||||
].forEach(async ({ value, visible }) => (
|
|
||||||
it(`discussion sidebar is ${visible ? 'shown' : 'hidden'} when localstorage value is ${value}`, async () => {
|
|
||||||
await setupDiscussionSidebar(value);
|
|
||||||
const element = await waitFor(() => screen.findByTestId('sidebar-DISCUSSIONS'));
|
|
||||||
if (visible) {
|
|
||||||
expect(element).not.toHaveClass('d-none');
|
|
||||||
} else {
|
|
||||||
expect(element).toHaveClass('d-none');
|
|
||||||
}
|
|
||||||
})));
|
|
||||||
|
|
||||||
[
|
|
||||||
{ value: true, result: 'false' },
|
|
||||||
{ value: false, result: 'true' },
|
|
||||||
].forEach(async ({ value, result }) => (
|
|
||||||
it(`Discussion sidebar storage value is ${!value} when sidebar is ${value ? 'closed' : 'open'}`, async () => {
|
|
||||||
await setupDiscussionSidebar(value);
|
|
||||||
await act(async () => {
|
|
||||||
const button = await screen.queryByRole('button', { name: /Show discussions tray/i });
|
|
||||||
button.click();
|
|
||||||
});
|
|
||||||
expect(localStorage.getItem('showDiscussionSidebar')).toBe(result);
|
|
||||||
})));
|
|
||||||
|
|
||||||
it('passes handlers to the sequence', async () => {
|
it('passes handlers to the sequence', async () => {
|
||||||
const nextSequenceHandler = jest.fn();
|
const nextSequenceHandler = jest.fn();
|
||||||
const previousSequenceHandler = jest.fn();
|
const previousSequenceHandler = jest.fn();
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MockAdapter from 'axios-mock-adapter';
|
import MockAdapter from 'axios-mock-adapter';
|
||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { Factory } from 'rosie';
|
import { Factory } from 'rosie';
|
||||||
import {
|
import {
|
||||||
render, screen, fireEvent, initializeTestStore, waitFor, authenticatedUser, logUnhandledRequests,
|
render, screen, fireEvent, initializeTestStore, waitFor, authenticatedUser, logUnhandledRequests,
|
||||||
} from '../../../setupTest';
|
} from '../../../setupTest';
|
||||||
import { BookmarkButton } from './index';
|
import { BookmarkButton } from './index';
|
||||||
import { getBookmarksBaseUrl } from './data/api';
|
|
||||||
|
|
||||||
describe('Bookmark Button', () => {
|
describe('Bookmark Button', () => {
|
||||||
let axiosMock;
|
let axiosMock;
|
||||||
@@ -32,8 +32,7 @@ describe('Bookmark Button', () => {
|
|||||||
mockData.unitId = nonBookmarkedUnitBlock.id;
|
mockData.unitId = nonBookmarkedUnitBlock.id;
|
||||||
|
|
||||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||||
const bookmarkUrl = getBookmarksBaseUrl();
|
const bookmarkUrl = `${getConfig().LMS_BASE_URL}/api/bookmarks/v1/bookmarks/`;
|
||||||
|
|
||||||
axiosMock.onPost(bookmarkUrl).reply(200, { });
|
axiosMock.onPost(bookmarkUrl).reply(200, { });
|
||||||
|
|
||||||
const bookmarkDeleteUrlRegExp = new RegExp(`${bookmarkUrl}*,*`);
|
const bookmarkDeleteUrlRegExp = new RegExp(`${bookmarkUrl}*,*`);
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||||
|
|
||||||
export const getBookmarksBaseUrl = () => `${getConfig().LMS_BASE_URL}/api/bookmarks/v1/bookmarks/`;
|
const bookmarksBaseUrl = `${getConfig().LMS_BASE_URL}/api/bookmarks/v1/bookmarks/`;
|
||||||
|
|
||||||
export async function createBookmark(usageId) {
|
export async function createBookmark(usageId) {
|
||||||
return getAuthenticatedHttpClient().post(getBookmarksBaseUrl(), { usage_id: usageId });
|
return getAuthenticatedHttpClient().post(bookmarksBaseUrl, { usage_id: usageId });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteBookmark(usageId) {
|
export async function deleteBookmark(usageId) {
|
||||||
const { username } = getAuthenticatedUser();
|
const { username } = getAuthenticatedUser();
|
||||||
return getAuthenticatedHttpClient().delete(`${getBookmarksBaseUrl()}${username},${usageId}/`);
|
return getAuthenticatedHttpClient().delete(`${bookmarksBaseUrl}${username},${usageId}/`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
useWindowSize,
|
useWindowSize,
|
||||||
} from '@edx/paragon';
|
} from '@edx/paragon';
|
||||||
|
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
import ClapsMobile from './assets/claps_280x201.gif';
|
import ClapsMobile from './assets/claps_280x201.gif';
|
||||||
import ClapsTablet from './assets/claps_456x328.gif';
|
import ClapsTablet from './assets/claps_456x328.gif';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
@@ -20,13 +19,12 @@ import { useModel } from '../../../generic/model-store';
|
|||||||
const CelebrationModal = ({
|
const CelebrationModal = ({
|
||||||
courseId, intl, isOpen, onClose, ...rest
|
courseId, intl, isOpen, onClose, ...rest
|
||||||
}) => {
|
}) => {
|
||||||
const { org, celebrations } = useModel('courseHomeMeta', courseId);
|
const { org } = useModel('courseHomeMeta', courseId);
|
||||||
const dispatch = useDispatch();
|
|
||||||
const wideScreen = useWindowSize().width >= breakpoints.small.minWidth;
|
const wideScreen = useWindowSize().width >= breakpoints.small.minWidth;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
recordFirstSectionCelebration(org, courseId, celebrations, dispatch);
|
recordFirstSectionCelebration(org, courseId);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|||||||
@@ -15,20 +15,9 @@ function handleNextSectionCelebration(sequenceId, nextSequenceId) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function recordFirstSectionCelebration(org, courseId, celebrations, dispatch) {
|
function recordFirstSectionCelebration(org, courseId) {
|
||||||
// Tell the LMS
|
// Tell the LMS
|
||||||
postCelebrationComplete(courseId, { first_section: false });
|
postCelebrationComplete(courseId, { first_section: false });
|
||||||
// Update our local copy of course data from LMS
|
|
||||||
dispatch(updateModel({
|
|
||||||
modelType: 'courseHomeMeta',
|
|
||||||
model: {
|
|
||||||
id: courseId,
|
|
||||||
celebrations: {
|
|
||||||
...celebrations,
|
|
||||||
firstSection: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Tell our analytics
|
// Tell our analytics
|
||||||
const { administrator } = getAuthenticatedUser();
|
const { administrator } = getAuthenticatedUser();
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import { recordFirstSectionCelebration } from './utils';
|
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform/analytics');
|
|
||||||
jest.mock('./data/api');
|
|
||||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
|
||||||
getAuthenticatedUser: jest.fn(() => ({ administrator: 'admin' })),
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('recordFirstSectionCelebration', () => {
|
|
||||||
it('updates the local copy of the course data from the LMS', async () => {
|
|
||||||
const dispatchMock = jest.fn();
|
|
||||||
recordFirstSectionCelebration('org', 'courseId', 'celebration', dispatchMock);
|
|
||||||
expect(dispatchMock).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
.content-tools {
|
.content-tools {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ describe('Sequence', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await waitFor(() => expect(screen.queryByText('Loading locked content messaging...')).toBeInTheDocument());
|
await waitFor(() => expect(screen.queryByText('Loading locked content messaging...')).toBeInTheDocument());
|
||||||
// `Previous`, `Active`, `Next`, `Prerequisite` and `Close Tray` buttons.
|
// `Previous`, `Active`, `Next` and `Prerequisite` buttons.
|
||||||
expect(screen.getAllByRole('button').length).toEqual(5);
|
expect(screen.getAllByRole('button').length).toEqual(4);
|
||||||
|
|
||||||
expect(screen.getByText('Content Locked')).toBeInTheDocument();
|
expect(screen.getByText('Content Locked')).toBeInTheDocument();
|
||||||
const unitContainer = container.querySelector('.unit-container');
|
const unitContainer = container.querySelector('.unit-container');
|
||||||
@@ -126,7 +126,7 @@ describe('Sequence', () => {
|
|||||||
render(<Sequence {...mockData} />);
|
render(<Sequence {...mockData} />);
|
||||||
expect(await screen.findByText('Loading learning sequence...')).toBeInTheDocument();
|
expect(await screen.findByText('Loading learning sequence...')).toBeInTheDocument();
|
||||||
// Renders navigation buttons plus one button for each unit.
|
// Renders navigation buttons plus one button for each unit.
|
||||||
expect(screen.getAllByRole('button')).toHaveLength(4 + unitBlocks.length);
|
expect(screen.getAllByRole('button')).toHaveLength(3 + unitBlocks.length);
|
||||||
|
|
||||||
loadUnit();
|
loadUnit();
|
||||||
await waitFor(() => expect(screen.queryByText('Loading learning sequence...')).not.toBeInTheDocument());
|
await waitFor(() => expect(screen.queryByText('Loading learning sequence...')).not.toBeInTheDocument());
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ const Unit = ({
|
|||||||
const [showError, setShowError] = useState(false);
|
const [showError, setShowError] = useState(false);
|
||||||
const [modalOptions, setModalOptions] = useState({ open: false });
|
const [modalOptions, setModalOptions] = useState({ open: false });
|
||||||
const [shouldDisplayHonorCode, setShouldDisplayHonorCode] = useState(false);
|
const [shouldDisplayHonorCode, setShouldDisplayHonorCode] = useState(false);
|
||||||
const [windowTopOffset, setWindowTopOffset] = useState(null);
|
|
||||||
|
|
||||||
const unit = useModel('units', id);
|
const unit = useModel('units', id);
|
||||||
const course = useModel('coursewareMeta', courseId);
|
const course = useModel('coursewareMeta', courseId);
|
||||||
@@ -121,13 +120,6 @@ const Unit = ({
|
|||||||
} = data;
|
} = data;
|
||||||
if (type === 'plugin.resize') {
|
if (type === 'plugin.resize') {
|
||||||
setIframeHeight(payload.height);
|
setIframeHeight(payload.height);
|
||||||
|
|
||||||
// We observe exit from the video xblock full screen mode
|
|
||||||
// and do page scroll to the previously saved scroll position
|
|
||||||
if (windowTopOffset !== null) {
|
|
||||||
window.scrollTo(0, Number(windowTopOffset));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasLoaded && iframeHeight === 0 && payload.height > 0) {
|
if (!hasLoaded && iframeHeight === 0 && payload.height > 0) {
|
||||||
setHasLoaded(true);
|
setHasLoaded(true);
|
||||||
if (onLoaded) {
|
if (onLoaded) {
|
||||||
@@ -137,16 +129,12 @@ const Unit = ({
|
|||||||
} else if (type === 'plugin.modal') {
|
} else if (type === 'plugin.modal') {
|
||||||
payload.open = true;
|
payload.open = true;
|
||||||
setModalOptions(payload);
|
setModalOptions(payload);
|
||||||
} else if (type === 'plugin.videoFullScreen') {
|
|
||||||
// We listen for this message from LMS to know when we need to
|
|
||||||
// save or reset scroll position on toggle video xblock full screen mode.
|
|
||||||
setWindowTopOffset(payload.open ? window.scrollY : null);
|
|
||||||
} else if (data.offset) {
|
} else if (data.offset) {
|
||||||
// We listen for this message from LMS to know when the page needs to
|
// We listen for this message from LMS to know when the page needs to
|
||||||
// be scrolled to another location on the page.
|
// be scrolled to another location on the page.
|
||||||
window.scrollTo(0, data.offset + document.getElementById('unit-iframe').offsetTop);
|
window.scrollTo(0, data.offset + document.getElementById('unit-iframe').offsetTop);
|
||||||
}
|
}
|
||||||
}, [id, setIframeHeight, hasLoaded, iframeHeight, setHasLoaded, onLoaded, setWindowTopOffset, windowTopOffset]);
|
}, [id, setIframeHeight, hasLoaded, iframeHeight, setHasLoaded, onLoaded]);
|
||||||
useEventListener('message', receiveMessage);
|
useEventListener('message', receiveMessage);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
sendUrlHashToFrame(document.getElementById('unit-iframe'));
|
sendUrlHashToFrame(document.getElementById('unit-iframe'));
|
||||||
|
|||||||
@@ -129,21 +129,6 @@ describe('Unit', () => {
|
|||||||
expect(window.scrollY === testMessageWithOffset.offset);
|
expect(window.scrollY === testMessageWithOffset.offset);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('scrolls page on MessagaeEvent when receiving videoFullScreen state', async () => {
|
|
||||||
// Set message to constain video full screen data.
|
|
||||||
const defaultTopOffset = 800;
|
|
||||||
const testMessageWithOtherHeight = { ...messageEvent, payload: { height: 500 } };
|
|
||||||
const testMessageWithFullscreenState = (isOpen) => ({ type: 'plugin.videoFullScreen', payload: { open: isOpen } });
|
|
||||||
render(<Unit {...mockData} />);
|
|
||||||
Object.defineProperty(window, 'scrollY', { value: defaultTopOffset, writable: true });
|
|
||||||
window.postMessage(testMessageWithFullscreenState(true), '*');
|
|
||||||
window.postMessage(testMessageWithFullscreenState(false), '*');
|
|
||||||
window.postMessage(testMessageWithOtherHeight, '*');
|
|
||||||
|
|
||||||
await expect(waitFor(() => expect(window.scrollTo()).toHaveBeenCalledTimes(1)));
|
|
||||||
expect(window.scrollY === defaultTopOffset);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('ignores MessageEvent with unhandled type', async () => {
|
it('ignores MessageEvent with unhandled type', async () => {
|
||||||
// Clone message and set different type.
|
// Clone message and set different type.
|
||||||
const testMessageWithUnhandledType = { ...messageEvent, type: 'wrong type' };
|
const testMessageWithUnhandledType = { ...messageEvent, type: 'wrong type' };
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { SIDEBAR_ORDER, SIDEBARS } from './sidebars';
|
import SidebarContext from './SidebarContext';
|
||||||
|
import { SIDEBARS } from './sidebars';
|
||||||
|
|
||||||
const Sidebar = () => (
|
const Sidebar = () => {
|
||||||
<>
|
const {
|
||||||
{
|
currentSidebar,
|
||||||
SIDEBAR_ORDER.map((sideBarId) => {
|
} = useContext(SidebarContext);
|
||||||
const SidebarToRender = SIDEBARS[sideBarId].Sidebar;
|
if (!currentSidebar) {
|
||||||
return <SidebarToRender />;
|
return null;
|
||||||
})
|
}
|
||||||
}
|
const CurrentSidebar = SIDEBARS[currentSidebar].Sidebar;
|
||||||
</>
|
return (
|
||||||
);
|
<CurrentSidebar />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Sidebar;
|
export default Sidebar;
|
||||||
|
|||||||
@@ -19,17 +19,9 @@ const SidebarProvider = ({
|
|||||||
const shouldDisplayFullScreen = useWindowSize().width < breakpoints.large.minWidth;
|
const shouldDisplayFullScreen = useWindowSize().width < breakpoints.large.minWidth;
|
||||||
const shouldDisplaySidebarOpen = useWindowSize().width > breakpoints.medium.minWidth;
|
const shouldDisplaySidebarOpen = useWindowSize().width > breakpoints.medium.minWidth;
|
||||||
const showNotificationsOnLoad = getSessionStorage(`notificationTrayStatus.${courseId}`) !== 'closed';
|
const showNotificationsOnLoad = getSessionStorage(`notificationTrayStatus.${courseId}`) !== 'closed';
|
||||||
const query = new URLSearchParams(window.location.search);
|
const initialSidebar = (verifiedMode && shouldDisplaySidebarOpen && showNotificationsOnLoad)
|
||||||
if (query.get('sidebar') === 'true') {
|
|
||||||
localStorage.setItem('showDiscussionSidebar', true);
|
|
||||||
}
|
|
||||||
const showDiscussionSidebar = localStorage.getItem('showDiscussionSidebar') !== 'false';
|
|
||||||
const showNotificationSidebar = (verifiedMode && shouldDisplaySidebarOpen && showNotificationsOnLoad)
|
|
||||||
? SIDEBARS.NOTIFICATIONS.ID
|
? SIDEBARS.NOTIFICATIONS.ID
|
||||||
: null;
|
: null;
|
||||||
const initialSidebar = showDiscussionSidebar
|
|
||||||
? SIDEBARS.DISCUSSIONS.ID
|
|
||||||
: showNotificationSidebar;
|
|
||||||
const [currentSidebar, setCurrentSidebar] = useState(initialSidebar);
|
const [currentSidebar, setCurrentSidebar] = useState(initialSidebar);
|
||||||
const [notificationStatus, setNotificationStatus] = useState(getLocalStorage(`notificationStatus.${courseId}`));
|
const [notificationStatus, setNotificationStatus] = useState(getLocalStorage(`notificationStatus.${courseId}`));
|
||||||
const [upgradeNotificationCurrentState, setUpgradeNotificationCurrentState] = useState(getLocalStorage(`upgradeNotificationCurrentState.${courseId}`));
|
const [upgradeNotificationCurrentState, setUpgradeNotificationCurrentState] = useState(getLocalStorage(`upgradeNotificationCurrentState.${courseId}`));
|
||||||
@@ -49,11 +41,6 @@ const SidebarProvider = ({
|
|||||||
|
|
||||||
const toggleSidebar = useCallback((sidebarId) => {
|
const toggleSidebar = useCallback((sidebarId) => {
|
||||||
// Switch to new sidebar or hide the current sidebar
|
// Switch to new sidebar or hide the current sidebar
|
||||||
if (currentSidebar === SIDEBARS.DISCUSSIONS.ID) {
|
|
||||||
localStorage.setItem('showDiscussionSidebar', false);
|
|
||||||
} else if (sidebarId === SIDEBARS.DISCUSSIONS.ID) {
|
|
||||||
localStorage.setItem('showDiscussionSidebar', true);
|
|
||||||
}
|
|
||||||
setCurrentSidebar(sidebarId === currentSidebar ? null : sidebarId);
|
setCurrentSidebar(sidebarId === currentSidebar ? null : sidebarId);
|
||||||
}, [currentSidebar]);
|
}, [currentSidebar]);
|
||||||
|
|
||||||
|
|||||||
@@ -34,14 +34,12 @@ const SidebarBase = ({
|
|||||||
|
|
||||||
useEventListener('message', receiveMessage);
|
useEventListener('message', receiveMessage);
|
||||||
|
|
||||||
return (
|
return currentSidebar === sidebarId && (
|
||||||
<section
|
<section
|
||||||
className={classNames('ml-0 ml-lg-4 border border-light-400 rounded-sm h-auto align-top', {
|
className={classNames('ml-0 ml-lg-4 border border-light-400 rounded-sm h-auto align-top', {
|
||||||
'bg-white m-0 border-0 fixed-top vh-100 rounded-0': shouldDisplayFullScreen,
|
'bg-white m-0 border-0 fixed-top vh-100 rounded-0': shouldDisplayFullScreen,
|
||||||
'min-vh-100': !shouldDisplayFullScreen,
|
'min-vh-100': !shouldDisplayFullScreen,
|
||||||
'd-none': currentSidebar !== sidebarId,
|
|
||||||
}, className)}
|
}, className)}
|
||||||
data-testid={`sidebar-${sidebarId}`}
|
|
||||||
style={{ width: shouldDisplayFullScreen ? '100%' : width }}
|
style={{ width: shouldDisplayFullScreen ? '100%' : width }}
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ const DiscussionsSidebar = ({ intl }) => {
|
|||||||
src={`${discussionsUrl}?inContextSidebar`}
|
src={`${discussionsUrl}?inContextSidebar`}
|
||||||
className="d-flex w-100 h-100 border-0"
|
className="d-flex w-100 h-100 border-0"
|
||||||
title={intl.formatMessage(messages.discussionsTitle)}
|
title={intl.formatMessage(messages.discussionsTitle)}
|
||||||
allow="clipboard-write"
|
|
||||||
loading="lazy"
|
|
||||||
/>
|
/>
|
||||||
</SidebarBase>
|
</SidebarBase>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -45,6 +45,25 @@ describe('Courseware Service', () => {
|
|||||||
|
|
||||||
describe('When a request to get a learning sequence outline is made', () => {
|
describe('When a request to get a learning sequence outline is made', () => {
|
||||||
it('returns a normalized outline', async () => {
|
it('returns a normalized outline', async () => {
|
||||||
|
await provider.addInteraction({
|
||||||
|
state: `Outline exists for course_id ${courseId}`,
|
||||||
|
uponReceiving: 'a request to get an outline',
|
||||||
|
withRequest: {
|
||||||
|
method: 'GET',
|
||||||
|
path: `/api/learning_sequences/v1/course_outline/${courseId}`,
|
||||||
|
},
|
||||||
|
willRespondWith: {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
course_key: string('course-v1:edX+DemoX+Demo_Course'),
|
||||||
|
title: string('Demo Course'),
|
||||||
|
outline: {
|
||||||
|
sections: [],
|
||||||
|
sequences: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
const normalizedOutline = {
|
const normalizedOutline = {
|
||||||
courses: {
|
courses: {
|
||||||
'course-v1:edX+DemoX+Demo_Course': {
|
'course-v1:edX+DemoX+Demo_Course': {
|
||||||
@@ -57,32 +76,74 @@ describe('Courseware Service', () => {
|
|||||||
sections: {},
|
sections: {},
|
||||||
sequences: {},
|
sequences: {},
|
||||||
};
|
};
|
||||||
setTimeout(() => {
|
const response = await getLearningSequencesOutline(courseId);
|
||||||
provider.addInteraction({
|
expect(response).toEqual(normalizedOutline);
|
||||||
state: `Outline exists for course_id ${courseId}`,
|
|
||||||
uponReceiving: 'a request to get an outline',
|
|
||||||
withRequest: {
|
|
||||||
method: 'GET',
|
|
||||||
path: `/api/learning_sequences/v1/course_outline/${courseId}`,
|
|
||||||
},
|
|
||||||
willRespondWith: {
|
|
||||||
status: 200,
|
|
||||||
body: {
|
|
||||||
course_key: string('course-v1:edX+DemoX+Demo_Course'),
|
|
||||||
title: string('Demo Course'),
|
|
||||||
outline: {
|
|
||||||
sections: [],
|
|
||||||
sequences: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const response = getLearningSequencesOutline(courseId);
|
|
||||||
expect(response).toEqual(normalizedOutline);
|
|
||||||
}, 100);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('skips unreleased sequences', async () => {
|
it('skips unreleased sequences', async () => {
|
||||||
|
await provider.addInteraction({
|
||||||
|
state: `Outline exists with unreleased sequences for course_id ${courseId}`,
|
||||||
|
uponReceiving: 'a request to get an outline',
|
||||||
|
withRequest: {
|
||||||
|
method: 'GET',
|
||||||
|
path: `/api/learning_sequences/v1/course_outline/${courseId}`,
|
||||||
|
},
|
||||||
|
willRespondWith: {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
course_key: string('course-v1:edX+DemoX+Demo_Course'),
|
||||||
|
title: string('Demo Course'),
|
||||||
|
outline: like({
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
id: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@partial',
|
||||||
|
title: 'Partially released',
|
||||||
|
sequence_ids: [
|
||||||
|
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@accessible',
|
||||||
|
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@released',
|
||||||
|
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@nope1',
|
||||||
|
],
|
||||||
|
effective_start: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@nope',
|
||||||
|
title: 'Wholly unreleased',
|
||||||
|
sequence_ids: [
|
||||||
|
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@nope2',
|
||||||
|
],
|
||||||
|
effective_start: '9999-07-01T17:00:00Z',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sequences: {
|
||||||
|
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@accessible': {
|
||||||
|
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@accessible',
|
||||||
|
title: 'Can access',
|
||||||
|
accessible: true,
|
||||||
|
effective_start: '9999-07-01T17:00:00Z',
|
||||||
|
},
|
||||||
|
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@released': {
|
||||||
|
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@released',
|
||||||
|
title: 'Released and inaccessible',
|
||||||
|
accessible: false,
|
||||||
|
effective_start: '2019-07-01T17:00:00Z',
|
||||||
|
},
|
||||||
|
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@nope1': {
|
||||||
|
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@nope1',
|
||||||
|
title: 'Unreleased',
|
||||||
|
accessible: false,
|
||||||
|
effective_start: '9999-07-01T17:00:00Z',
|
||||||
|
},
|
||||||
|
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@nope2': {
|
||||||
|
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@nope2',
|
||||||
|
title: 'Still unreleased',
|
||||||
|
accessible: false,
|
||||||
|
effective_start: '9999-07-01T17:00:00Z',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
const normalizedOutline = {
|
const normalizedOutline = {
|
||||||
courses: {
|
courses: {
|
||||||
'course-v1:edX+DemoX+Demo_Course': {
|
'course-v1:edX+DemoX+Demo_Course': {
|
||||||
@@ -118,78 +179,120 @@ describe('Courseware Service', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
setTimeout(() => {
|
const response = await getLearningSequencesOutline(courseId);
|
||||||
provider.addInteraction({
|
expect(response).toEqual(normalizedOutline);
|
||||||
state: `Outline exists with unreleased sequences for course_id ${courseId}`,
|
|
||||||
uponReceiving: 'a request to get an outline',
|
|
||||||
withRequest: {
|
|
||||||
method: 'GET',
|
|
||||||
path: `/api/learning_sequences/v1/course_outline/${courseId}`,
|
|
||||||
},
|
|
||||||
willRespondWith: {
|
|
||||||
status: 200,
|
|
||||||
body: {
|
|
||||||
course_key: string('course-v1:edX+DemoX+Demo_Course'),
|
|
||||||
title: string('Demo Course'),
|
|
||||||
outline: like({
|
|
||||||
sections: [
|
|
||||||
{
|
|
||||||
id: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@partial',
|
|
||||||
title: 'Partially released',
|
|
||||||
sequence_ids: [
|
|
||||||
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@accessible',
|
|
||||||
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@released',
|
|
||||||
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@nope1',
|
|
||||||
],
|
|
||||||
effective_start: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@nope',
|
|
||||||
title: 'Wholly unreleased',
|
|
||||||
sequence_ids: [
|
|
||||||
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@nope2',
|
|
||||||
],
|
|
||||||
effective_start: '9999-07-01T17:00:00Z',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
sequences: {
|
|
||||||
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@accessible': {
|
|
||||||
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@accessible',
|
|
||||||
title: 'Can access',
|
|
||||||
accessible: true,
|
|
||||||
effective_start: '9999-07-01T17:00:00Z',
|
|
||||||
},
|
|
||||||
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@released': {
|
|
||||||
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@released',
|
|
||||||
title: 'Released and inaccessible',
|
|
||||||
accessible: false,
|
|
||||||
effective_start: '2019-07-01T17:00:00Z',
|
|
||||||
},
|
|
||||||
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@nope1': {
|
|
||||||
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@nope1',
|
|
||||||
title: 'Unreleased',
|
|
||||||
accessible: false,
|
|
||||||
effective_start: '9999-07-01T17:00:00Z',
|
|
||||||
},
|
|
||||||
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@nope2': {
|
|
||||||
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@nope2',
|
|
||||||
title: 'Still unreleased',
|
|
||||||
accessible: false,
|
|
||||||
effective_start: '9999-07-01T17:00:00Z',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const response = getLearningSequencesOutline(courseId);
|
|
||||||
expect(response).toEqual(normalizedOutline);
|
|
||||||
}, 100);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When a request to get course metadata is made', () => {
|
describe('When a request to get course metadata is made', () => {
|
||||||
it('returns normalized course metadata', () => {
|
it('returns normalized course metadata', async () => {
|
||||||
|
await provider.addInteraction({
|
||||||
|
state: `course metadata exists for course_id ${courseId}`,
|
||||||
|
uponReceiving: 'a request to get course metadata',
|
||||||
|
withRequest: {
|
||||||
|
method: 'GET',
|
||||||
|
path: `/api/courseware/course/${courseId}`,
|
||||||
|
query: {
|
||||||
|
browser_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
willRespondWith: {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
access_expiration: {
|
||||||
|
expiration_date: term({
|
||||||
|
generate: '2013-02-05T05:00:00Z',
|
||||||
|
matcher: dateRegex,
|
||||||
|
}),
|
||||||
|
masquerading_expired_course: boolean(false),
|
||||||
|
upgrade_deadline: term({
|
||||||
|
generate: '2013-02-05T05:00:00Z',
|
||||||
|
matcher: dateRegex,
|
||||||
|
}),
|
||||||
|
upgrade_url: string('link'),
|
||||||
|
},
|
||||||
|
can_show_upgrade_sock: boolean(false),
|
||||||
|
content_type_gating_enabled: boolean(false),
|
||||||
|
end: term({
|
||||||
|
generate: '2013-02-05T05:00:00Z',
|
||||||
|
matcher: dateRegex,
|
||||||
|
}),
|
||||||
|
enrollment: {
|
||||||
|
mode: term({
|
||||||
|
generate: 'audit',
|
||||||
|
matcher: '^(audit|verified)$',
|
||||||
|
}),
|
||||||
|
is_active: boolean(true),
|
||||||
|
},
|
||||||
|
enrollment_start: term({
|
||||||
|
generate: '2013-02-05T05:00:00Z',
|
||||||
|
matcher: dateRegex,
|
||||||
|
}),
|
||||||
|
enrollment_end: term({
|
||||||
|
generate: '2013-02-05T05:00:00Z',
|
||||||
|
matcher: dateRegex,
|
||||||
|
}),
|
||||||
|
id: term({
|
||||||
|
generate: 'course-v1:edX+DemoX+Demo_Course',
|
||||||
|
matcher: opaqueKeysRegex,
|
||||||
|
}),
|
||||||
|
license: string('all-rights-reserved'),
|
||||||
|
name: like('Demonstration Course'),
|
||||||
|
offer: {
|
||||||
|
code: string('code'),
|
||||||
|
expiration_date: term({
|
||||||
|
generate: '2013-02-05T05:00:00Z',
|
||||||
|
matcher: dateRegex,
|
||||||
|
}),
|
||||||
|
original_price: string('$99'),
|
||||||
|
discounted_price: string('$99'),
|
||||||
|
percentage: integer(50),
|
||||||
|
upgrade_url: string('url'),
|
||||||
|
},
|
||||||
|
related_programs: null,
|
||||||
|
short_description: like(''),
|
||||||
|
start: term({
|
||||||
|
generate: '2013-02-05T05:00:00Z',
|
||||||
|
matcher: dateRegex,
|
||||||
|
}),
|
||||||
|
user_timezone: null,
|
||||||
|
verified_mode: like({
|
||||||
|
access_expiration_date: term({
|
||||||
|
generate: '2013-02-05T05:00:00Z',
|
||||||
|
matcher: dateRegex,
|
||||||
|
}),
|
||||||
|
currency: 'USD',
|
||||||
|
currency_symbol: '$',
|
||||||
|
price: 149,
|
||||||
|
sku: '8CF08E5',
|
||||||
|
upgrade_url: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
|
||||||
|
}),
|
||||||
|
show_calculator: boolean(false),
|
||||||
|
original_user_is_staff: boolean(true),
|
||||||
|
is_staff: boolean(true),
|
||||||
|
course_access: like({
|
||||||
|
has_access: true,
|
||||||
|
error_code: null,
|
||||||
|
developer_message: null,
|
||||||
|
user_message: null,
|
||||||
|
additional_context_user_message: null,
|
||||||
|
user_fragment: null,
|
||||||
|
}),
|
||||||
|
notes: { enabled: boolean(false), visible: boolean(true) },
|
||||||
|
marketing_url: null,
|
||||||
|
user_has_passing_grade: boolean(false),
|
||||||
|
course_exit_page_is_active: boolean(false),
|
||||||
|
certificate_data: {
|
||||||
|
cert_status: string('audit_passing'), cert_web_view_url: null, certificate_available_date: null,
|
||||||
|
},
|
||||||
|
verify_identity_url: null,
|
||||||
|
verification_status: string('none'),
|
||||||
|
linkedin_add_to_profile_url: null,
|
||||||
|
user_needs_integrity_signature: boolean(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const normalizedCourseMetadata = {
|
const normalizedCourseMetadata = {
|
||||||
accessExpiration: {
|
accessExpiration: {
|
||||||
expirationDate: '2013-02-05T05:00:00Z',
|
expirationDate: '2013-02-05T05:00:00Z',
|
||||||
@@ -234,122 +337,56 @@ describe('Courseware Service', () => {
|
|||||||
relatedPrograms: null,
|
relatedPrograms: null,
|
||||||
userNeedsIntegritySignature: false,
|
userNeedsIntegritySignature: false,
|
||||||
};
|
};
|
||||||
setTimeout(() => {
|
const response = await getCourseMetadata(courseId);
|
||||||
provider.addInteraction({
|
expect(response).toBeTruthy();
|
||||||
state: `course metadata exists for course_id ${courseId}`,
|
expect(response).toEqual(normalizedCourseMetadata);
|
||||||
uponReceiving: 'a request to get course metadata',
|
|
||||||
withRequest: {
|
|
||||||
method: 'GET',
|
|
||||||
path: `/api/courseware/course/${courseId}`,
|
|
||||||
query: {
|
|
||||||
browser_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
willRespondWith: {
|
|
||||||
status: 200,
|
|
||||||
body: {
|
|
||||||
access_expiration: {
|
|
||||||
expiration_date: term({
|
|
||||||
generate: '2013-02-05T05:00:00Z',
|
|
||||||
matcher: dateRegex,
|
|
||||||
}),
|
|
||||||
masquerading_expired_course: boolean(false),
|
|
||||||
upgrade_deadline: term({
|
|
||||||
generate: '2013-02-05T05:00:00Z',
|
|
||||||
matcher: dateRegex,
|
|
||||||
}),
|
|
||||||
upgrade_url: string('link'),
|
|
||||||
},
|
|
||||||
can_show_upgrade_sock: boolean(false),
|
|
||||||
content_type_gating_enabled: boolean(false),
|
|
||||||
end: term({
|
|
||||||
generate: '2013-02-05T05:00:00Z',
|
|
||||||
matcher: dateRegex,
|
|
||||||
}),
|
|
||||||
enrollment: {
|
|
||||||
mode: term({
|
|
||||||
generate: 'audit',
|
|
||||||
matcher: '^(audit|verified)$',
|
|
||||||
}),
|
|
||||||
is_active: boolean(true),
|
|
||||||
},
|
|
||||||
enrollment_start: term({
|
|
||||||
generate: '2013-02-05T05:00:00Z',
|
|
||||||
matcher: dateRegex,
|
|
||||||
}),
|
|
||||||
enrollment_end: term({
|
|
||||||
generate: '2013-02-05T05:00:00Z',
|
|
||||||
matcher: dateRegex,
|
|
||||||
}),
|
|
||||||
id: term({
|
|
||||||
generate: 'course-v1:edX+DemoX+Demo_Course',
|
|
||||||
matcher: opaqueKeysRegex,
|
|
||||||
}),
|
|
||||||
license: string('all-rights-reserved'),
|
|
||||||
name: like('Demonstration Course'),
|
|
||||||
offer: {
|
|
||||||
code: string('code'),
|
|
||||||
expiration_date: term({
|
|
||||||
generate: '2013-02-05T05:00:00Z',
|
|
||||||
matcher: dateRegex,
|
|
||||||
}),
|
|
||||||
original_price: string('$99'),
|
|
||||||
discounted_price: string('$99'),
|
|
||||||
percentage: integer(50),
|
|
||||||
upgrade_url: string('url'),
|
|
||||||
},
|
|
||||||
related_programs: null,
|
|
||||||
short_description: like(''),
|
|
||||||
start: term({
|
|
||||||
generate: '2013-02-05T05:00:00Z',
|
|
||||||
matcher: dateRegex,
|
|
||||||
}),
|
|
||||||
user_timezone: null,
|
|
||||||
verified_mode: like({
|
|
||||||
access_expiration_date: term({
|
|
||||||
generate: '2013-02-05T05:00:00Z',
|
|
||||||
matcher: dateRegex,
|
|
||||||
}),
|
|
||||||
currency: 'USD',
|
|
||||||
currency_symbol: '$',
|
|
||||||
price: 149,
|
|
||||||
sku: '8CF08E5',
|
|
||||||
upgrade_url: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
|
|
||||||
}),
|
|
||||||
show_calculator: boolean(false),
|
|
||||||
original_user_is_staff: boolean(true),
|
|
||||||
is_staff: boolean(true),
|
|
||||||
course_access: like({
|
|
||||||
has_access: true,
|
|
||||||
error_code: null,
|
|
||||||
developer_message: null,
|
|
||||||
user_message: null,
|
|
||||||
additional_context_user_message: null,
|
|
||||||
user_fragment: null,
|
|
||||||
}),
|
|
||||||
notes: { enabled: boolean(false), visible: boolean(true) },
|
|
||||||
marketing_url: null,
|
|
||||||
user_has_passing_grade: boolean(false),
|
|
||||||
course_exit_page_is_active: boolean(false),
|
|
||||||
certificate_data: {
|
|
||||||
cert_status: string('audit_passing'), cert_web_view_url: null, certificate_available_date: null,
|
|
||||||
},
|
|
||||||
verify_identity_url: null,
|
|
||||||
verification_status: string('none'),
|
|
||||||
linkedin_add_to_profile_url: null,
|
|
||||||
user_needs_integrity_signature: boolean(false),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const response = getCourseMetadata(courseId);
|
|
||||||
expect(response).toBeTruthy();
|
|
||||||
expect(response).toEqual(normalizedCourseMetadata);
|
|
||||||
}, 100);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When a request to get sequence metadata is made', () => {
|
describe('When a request to get sequence metadata is made', () => {
|
||||||
it('returns normalized sequence metadata ', () => {
|
it('returns normalized sequence metadata ', async () => {
|
||||||
|
await provider.addInteraction({
|
||||||
|
state: `sequence metadata data exists for sequence_id ${sequenceId}`,
|
||||||
|
uponReceiving: 'a request to get sequence metadata',
|
||||||
|
withRequest: {
|
||||||
|
method: 'GET',
|
||||||
|
path: `/api/courseware/sequence/${sequenceId}`,
|
||||||
|
},
|
||||||
|
willRespondWith: {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
items: eachLike({
|
||||||
|
content: '',
|
||||||
|
page_title: 'Pointing on a Picture',
|
||||||
|
type: 'problem',
|
||||||
|
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@2152d4a4aadc4cb0af5256394a3d1fc7',
|
||||||
|
bookmarked: false,
|
||||||
|
path: 'Example Week 1: Getting Started > Homework - Question Styles > Pointing on a Picture',
|
||||||
|
graded: true,
|
||||||
|
contains_content_type_gated_content: false,
|
||||||
|
href: '',
|
||||||
|
}),
|
||||||
|
item_id: string('block-v1:edX+DemoX+Demo_Course+type@sequential+block@basic_questions'),
|
||||||
|
is_time_limited: boolean(false),
|
||||||
|
is_proctored: boolean(false),
|
||||||
|
is_hidden_after_due: boolean(false),
|
||||||
|
position: null,
|
||||||
|
tag: boolean('sequential'),
|
||||||
|
banner_text: null,
|
||||||
|
save_position: boolean(false),
|
||||||
|
show_completion: boolean(false),
|
||||||
|
gated_content: like({
|
||||||
|
prereq_id: null,
|
||||||
|
prereq_url: null,
|
||||||
|
prereq_section_name: null,
|
||||||
|
gated: false,
|
||||||
|
gated_section_name: 'Homework - Question Styles',
|
||||||
|
}),
|
||||||
|
display_name: boolean('Homework - Question Styles'),
|
||||||
|
format: boolean('Homework'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
const normalizedSequenceMetadata = {
|
const normalizedSequenceMetadata = {
|
||||||
sequence: {
|
sequence: {
|
||||||
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@basic_questions',
|
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@basic_questions',
|
||||||
@@ -386,154 +423,102 @@ describe('Courseware Service', () => {
|
|||||||
containsContentTypeGatedContent: false,
|
containsContentTypeGatedContent: false,
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
setTimeout(() => {
|
const response = await getSequenceMetadata(sequenceId);
|
||||||
provider.addInteraction({
|
expect(response).toBeTruthy();
|
||||||
state: `sequence metadata data exists for sequence_id ${sequenceId}`,
|
expect(response).toEqual(normalizedSequenceMetadata);
|
||||||
uponReceiving: 'a request to get sequence metadata',
|
|
||||||
withRequest: {
|
|
||||||
method: 'GET',
|
|
||||||
path: `/api/courseware/sequence/${sequenceId}`,
|
|
||||||
},
|
|
||||||
willRespondWith: {
|
|
||||||
status: 200,
|
|
||||||
body: {
|
|
||||||
items: eachLike({
|
|
||||||
content: '',
|
|
||||||
page_title: 'Pointing on a Picture',
|
|
||||||
type: 'problem',
|
|
||||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@2152d4a4aadc4cb0af5256394a3d1fc7',
|
|
||||||
bookmarked: false,
|
|
||||||
path: 'Example Week 1: Getting Started > Homework - Question Styles > Pointing on a Picture',
|
|
||||||
graded: true,
|
|
||||||
contains_content_type_gated_content: false,
|
|
||||||
href: '',
|
|
||||||
}),
|
|
||||||
item_id: string('block-v1:edX+DemoX+Demo_Course+type@sequential+block@basic_questions'),
|
|
||||||
is_time_limited: boolean(false),
|
|
||||||
is_proctored: boolean(false),
|
|
||||||
is_hidden_after_due: boolean(false),
|
|
||||||
position: null,
|
|
||||||
tag: boolean('sequential'),
|
|
||||||
banner_text: null,
|
|
||||||
save_position: boolean(false),
|
|
||||||
show_completion: boolean(false),
|
|
||||||
gated_content: like({
|
|
||||||
prereq_id: null,
|
|
||||||
prereq_url: null,
|
|
||||||
prereq_section_name: null,
|
|
||||||
gated: false,
|
|
||||||
gated_section_name: 'Homework - Question Styles',
|
|
||||||
}),
|
|
||||||
display_name: boolean('Homework - Question Styles'),
|
|
||||||
format: boolean('Homework'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const response = getSequenceMetadata(sequenceId);
|
|
||||||
expect(response).toBeTruthy();
|
|
||||||
expect(response).toEqual(normalizedSequenceMetadata);
|
|
||||||
}, 100);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When a request to set sequence position against Unit Index is made', () => {
|
describe('When a request to set sequence position against Unit Index is made', () => {
|
||||||
it('returns if the request was success or failure', async () => {
|
it('returns if the request was success or failure', async () => {
|
||||||
setTimeout(() => {
|
await provider.addInteraction({
|
||||||
provider.addInteraction({
|
state: `sequence position data exists for course_id ${courseId}, sequence_id ${sequenceId} and activeUnitIndex 0`,
|
||||||
state: `sequence position data exists for course_id ${courseId}, sequence_id ${sequenceId} and activeUnitIndex 0`,
|
uponReceiving: 'a request to set sequence position against activeUnitIndex',
|
||||||
uponReceiving: 'a request to set sequence position against activeUnitIndex',
|
withRequest: {
|
||||||
withRequest: {
|
method: 'POST',
|
||||||
method: 'POST',
|
path: `/courses/${courseId}/xblock/${sequenceId}/handler/goto_position`,
|
||||||
path: `/courses/${courseId}/xblock/${sequenceId}/handler/goto_position`,
|
body: { position: 1 }, // Position is 1-indexed on the provider side and 0-indexed in the consumer side.
|
||||||
body: { position: 1 }, // Position is 1-indexed on the provider side and 0-indexed in the consumer side.
|
},
|
||||||
|
willRespondWith: {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
success: boolean(true),
|
||||||
},
|
},
|
||||||
willRespondWith: {
|
},
|
||||||
status: 200,
|
});
|
||||||
body: {
|
const response = await postSequencePosition(courseId, sequenceId, 0);
|
||||||
success: boolean(true),
|
expect(response).toBeTruthy();
|
||||||
},
|
expect(response).toEqual({ success: true });
|
||||||
},
|
|
||||||
});
|
|
||||||
const response = postSequencePosition(courseId, sequenceId, 0);
|
|
||||||
expect(response).toBeTruthy();
|
|
||||||
expect(response).toEqual({ success: true });
|
|
||||||
}, 100);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When a request to get completion block is made', () => {
|
describe('When a request to get completion block is made', () => {
|
||||||
it('returns the completion status', async () => {
|
it('returns the completion status', async () => {
|
||||||
setTimeout(() => {
|
await provider.addInteraction({
|
||||||
provider.addInteraction({
|
state: `completion block data exists for course_id ${courseId}, sequence_id ${sequenceId} and usageId ${usageId}`,
|
||||||
state: `completion block data exists for course_id ${courseId}, sequence_id ${sequenceId} and usageId ${usageId}`,
|
uponReceiving: 'a request to get completion block',
|
||||||
uponReceiving: 'a request to get completion block',
|
withRequest: {
|
||||||
withRequest: {
|
method: 'POST',
|
||||||
method: 'POST',
|
path: `/courses/${courseId}/xblock/${sequenceId}/handler/get_completion`,
|
||||||
path: `/courses/${courseId}/xblock/${sequenceId}/handler/get_completion`,
|
body: { usage_key: usageId },
|
||||||
body: { usage_key: usageId },
|
},
|
||||||
|
willRespondWith: {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
complete: boolean(true),
|
||||||
},
|
},
|
||||||
willRespondWith: {
|
},
|
||||||
status: 200,
|
});
|
||||||
body: {
|
const response = await getBlockCompletion(courseId, sequenceId, usageId);
|
||||||
complete: boolean(true),
|
expect(response).toBeTruthy();
|
||||||
},
|
expect(response).toEqual(true);
|
||||||
},
|
|
||||||
});
|
|
||||||
const response = getBlockCompletion(courseId, sequenceId, usageId);
|
|
||||||
expect(response).toBeTruthy();
|
|
||||||
expect(response).toEqual(true);
|
|
||||||
}, 100);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When a request to get resume block is made', () => {
|
describe('When a request to get resume block is made', () => {
|
||||||
it('returns block id, section id and unit id of the resume block', async () => {
|
it('returns block id, section id and unit id of the resume block', async () => {
|
||||||
|
await provider.addInteraction({
|
||||||
|
state: `Resume block exists for course_id ${courseId}`,
|
||||||
|
uponReceiving: 'a request to get Resume block',
|
||||||
|
withRequest: {
|
||||||
|
method: 'GET',
|
||||||
|
path: `/api/courseware/resume/${courseId}`,
|
||||||
|
},
|
||||||
|
willRespondWith: {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
block_id: string('642fadf46d074aabb637f20af320fb31'),
|
||||||
|
section_id: string('642fadf46d074aabb637f20af320fb87'),
|
||||||
|
unit_id: string('642fadf46d074aabb637f20af320fb99'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
const camelCaseResponse = {
|
const camelCaseResponse = {
|
||||||
blockId: '642fadf46d074aabb637f20af320fb31',
|
blockId: '642fadf46d074aabb637f20af320fb31',
|
||||||
sectionId: '642fadf46d074aabb637f20af320fb87',
|
sectionId: '642fadf46d074aabb637f20af320fb87',
|
||||||
unitId: '642fadf46d074aabb637f20af320fb99',
|
unitId: '642fadf46d074aabb637f20af320fb99',
|
||||||
};
|
};
|
||||||
setTimeout(() => {
|
const response = await getResumeBlock(courseId);
|
||||||
provider.addInteraction({
|
expect(response).toBeTruthy();
|
||||||
state: `Resume block exists for course_id ${courseId}`,
|
expect(response).toEqual(camelCaseResponse);
|
||||||
uponReceiving: 'a request to get Resume block',
|
|
||||||
withRequest: {
|
|
||||||
method: 'GET',
|
|
||||||
path: `/api/courseware/resume/${courseId}`,
|
|
||||||
},
|
|
||||||
willRespondWith: {
|
|
||||||
status: 200,
|
|
||||||
body: {
|
|
||||||
block_id: string('642fadf46d074aabb637f20af320fb31'),
|
|
||||||
section_id: string('642fadf46d074aabb637f20af320fb87'),
|
|
||||||
unit_id: string('642fadf46d074aabb637f20af320fb99'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const response = getResumeBlock(courseId);
|
|
||||||
expect(response).toBeTruthy();
|
|
||||||
expect(response).toEqual(camelCaseResponse);
|
|
||||||
}, 100);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When a request to send activation email is made', () => {
|
describe('When a request to send activation email is made', () => {
|
||||||
it('returns status code 200', () => {
|
it('returns status code 200', async () => {
|
||||||
setTimeout(() => {
|
await provider.addInteraction({
|
||||||
provider.addInteraction({
|
state: 'A logged-in user may or may not be active',
|
||||||
state: 'A logged-in user may or may not be active',
|
uponReceiving: 'a request to send activation email',
|
||||||
uponReceiving: 'a request to send activation email',
|
withRequest: {
|
||||||
withRequest: {
|
method: 'POST',
|
||||||
method: 'POST',
|
path: '/api/send_account_activation_email',
|
||||||
path: '/api/send_account_activation_email',
|
},
|
||||||
},
|
willRespondWith: {
|
||||||
willRespondWith: {
|
status: 200,
|
||||||
status: 200,
|
},
|
||||||
},
|
});
|
||||||
});
|
const response = await sendActivationEmail();
|
||||||
const response = sendActivationEmail();
|
expect(response).toEqual('');
|
||||||
expect(response).toEqual('');
|
|
||||||
}, 100);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`DecodePageRoute should not modify the url if it does not need to be decoded 1`] = `
|
|
||||||
<div>
|
|
||||||
PageRoute: {
|
|
||||||
"computedMatch": {
|
|
||||||
"path": "/course/:courseId/home",
|
|
||||||
"url": "/course/course-v1:edX+DemoX+Demo_Course/home",
|
|
||||||
"isExact": true,
|
|
||||||
"params": {
|
|
||||||
"courseId": "course-v1:edX+DemoX+Demo_Course"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import { PageRoute } from '@edx/frontend-platform/react';
|
|
||||||
import React from 'react';
|
|
||||||
import { useHistory, generatePath } from 'react-router';
|
|
||||||
|
|
||||||
export const decodeUrl = (encodedUrl) => {
|
|
||||||
const decodedUrl = decodeURIComponent(encodedUrl);
|
|
||||||
if (encodedUrl === decodedUrl) {
|
|
||||||
return encodedUrl;
|
|
||||||
}
|
|
||||||
return decodeUrl(decodedUrl);
|
|
||||||
};
|
|
||||||
|
|
||||||
const DecodePageRoute = (props) => {
|
|
||||||
const history = useHistory();
|
|
||||||
if (props.computedMatch) {
|
|
||||||
const { url, path, params } = props.computedMatch;
|
|
||||||
|
|
||||||
Object.keys(params).forEach((param) => {
|
|
||||||
// only decode params not the entire url.
|
|
||||||
// it is just to be safe and less prone to errors
|
|
||||||
params[param] = decodeUrl(params[param]);
|
|
||||||
});
|
|
||||||
|
|
||||||
const newUrl = generatePath(path, params);
|
|
||||||
|
|
||||||
// if the url get decoded, reroute to the decoded url
|
|
||||||
if (newUrl !== url) {
|
|
||||||
history.replace(newUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return <PageRoute {...props} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
DecodePageRoute.propTypes = {
|
|
||||||
computedMatch: PropTypes.shape({
|
|
||||||
url: PropTypes.string.isRequired,
|
|
||||||
path: PropTypes.string.isRequired,
|
|
||||||
// eslint-disable-next-line react/forbid-prop-types
|
|
||||||
params: PropTypes.any,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
DecodePageRoute.defaultProps = {
|
|
||||||
computedMatch: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DecodePageRoute;
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { render } from '@testing-library/react';
|
|
||||||
import { createMemoryHistory } from 'history';
|
|
||||||
import { Router, matchPath } from 'react-router';
|
|
||||||
import DecodePageRoute, { decodeUrl } from '.';
|
|
||||||
|
|
||||||
const decodedCourseId = 'course-v1:edX+DemoX+Demo_Course';
|
|
||||||
const encodedCourseId = encodeURIComponent(decodedCourseId);
|
|
||||||
const deepEncodedCourseId = (() => {
|
|
||||||
let path = encodedCourseId;
|
|
||||||
for (let i = 0; i < 5; i++) {
|
|
||||||
path = encodeURIComponent(path);
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
})();
|
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform/react', () => ({
|
|
||||||
PageRoute: (props) => `PageRoute: ${JSON.stringify(props, null, 2)}`,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const renderPage = (props) => {
|
|
||||||
const memHistory = createMemoryHistory({
|
|
||||||
initialEntries: [props?.path],
|
|
||||||
});
|
|
||||||
|
|
||||||
const history = {
|
|
||||||
...memHistory,
|
|
||||||
replace: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const { container } = render(
|
|
||||||
<Router history={history}>
|
|
||||||
<DecodePageRoute computedMatch={props} />
|
|
||||||
</Router>,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
container,
|
|
||||||
history,
|
|
||||||
props,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('DecodePageRoute', () => {
|
|
||||||
it('should not modify the url if it does not need to be decoded', () => {
|
|
||||||
const props = matchPath(`/course/${decodedCourseId}/home`, {
|
|
||||||
path: '/course/:courseId/home',
|
|
||||||
});
|
|
||||||
const { container, history } = renderPage(props);
|
|
||||||
|
|
||||||
expect(props.url).toContain(decodedCourseId);
|
|
||||||
expect(history.replace).not.toHaveBeenCalled();
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should decode the url and replace the history if necessary', () => {
|
|
||||||
const props = matchPath(`/course/${encodedCourseId}/home`, {
|
|
||||||
path: '/course/:courseId/home',
|
|
||||||
});
|
|
||||||
const { history } = renderPage(props);
|
|
||||||
|
|
||||||
expect(props.url).not.toContain(decodedCourseId);
|
|
||||||
expect(props.url).toContain(encodedCourseId);
|
|
||||||
expect(history.replace.mock.calls[0][0]).toContain(decodedCourseId);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should decode the url multiple times if necessary', () => {
|
|
||||||
const props = matchPath(`/course/${deepEncodedCourseId}/home`, {
|
|
||||||
path: '/course/:courseId/home',
|
|
||||||
});
|
|
||||||
const { history } = renderPage(props);
|
|
||||||
|
|
||||||
expect(props.url).not.toContain(decodedCourseId);
|
|
||||||
expect(props.url).toContain(deepEncodedCourseId);
|
|
||||||
expect(history.replace.mock.calls[0][0]).toContain(decodedCourseId);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should only decode the url params and not the entire url', () => {
|
|
||||||
const decodedUnitId = 'some+thing';
|
|
||||||
const encodedUnitId = encodeURIComponent(decodedUnitId);
|
|
||||||
const props = matchPath(`/course/${deepEncodedCourseId}/${encodedUnitId}/${encodedUnitId}`, {
|
|
||||||
path: `/course/:courseId/${encodedUnitId}/:unitId`,
|
|
||||||
});
|
|
||||||
const { history } = renderPage(props);
|
|
||||||
|
|
||||||
const decodedUrls = history.replace.mock.calls[0][0].split('/');
|
|
||||||
|
|
||||||
// unitId get decoded
|
|
||||||
expect(decodedUrls.pop()).toContain(decodedUnitId);
|
|
||||||
|
|
||||||
// path remain encoded
|
|
||||||
expect(decodedUrls.pop()).toContain(encodedUnitId);
|
|
||||||
|
|
||||||
// courseId get decoded
|
|
||||||
expect(decodedUrls.pop()).toContain(decodedCourseId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('decodeUrl', () => {
|
|
||||||
expect(decodeUrl(decodedCourseId)).toEqual(decodedCourseId);
|
|
||||||
expect(decodeUrl(encodedCourseId)).toEqual(decodedCourseId);
|
|
||||||
expect(decodeUrl(deepEncodedCourseId)).toEqual(decodedCourseId);
|
|
||||||
});
|
|
||||||
@@ -10,19 +10,16 @@ import { getNotices } from './api';
|
|||||||
*/
|
*/
|
||||||
const NoticesProvider = ({ children }) => {
|
const NoticesProvider = ({ children }) => {
|
||||||
const [isRedirected, setIsRedirected] = useState();
|
const [isRedirected, setIsRedirected] = useState();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
useEffect(() => {
|
useEffect(async () => {
|
||||||
async function getData() {
|
if (getConfig().ENABLE_NOTICES) {
|
||||||
if (getConfig().ENABLE_NOTICES) {
|
const data = await getNotices();
|
||||||
const data = await getNotices();
|
if (data && data.results && data.results.length > 0) {
|
||||||
if (data && data.results && data.results.length > 0) {
|
const { results } = data;
|
||||||
const { results } = data;
|
setIsRedirected(true);
|
||||||
setIsRedirected(true);
|
window.location.replace(`${results[0]}?next=${window.location.href}`);
|
||||||
window.location.replace(`${results[0]}?next=${window.location.href}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getData();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -114,8 +114,7 @@
|
|||||||
"learning.proctoringPanel.onboardingButtonNotOpen": "يفتح الامتحان التحضيري في: {releaseDate}",
|
"learning.proctoringPanel.onboardingButtonNotOpen": "يفتح الامتحان التحضيري في: {releaseDate}",
|
||||||
"learning.proctoringPanel.reviewRequirementsButton": "مراجعة التعليمات و متطلبات النظام",
|
"learning.proctoringPanel.reviewRequirementsButton": "مراجعة التعليمات و متطلبات النظام",
|
||||||
"learning.proctoringPanel.onboardingButtonPastDue": "انقضى أجَل الامتحان التحضيري",
|
"learning.proctoringPanel.onboardingButtonPastDue": "انقضى أجَل الامتحان التحضيري",
|
||||||
"learning.outline.sequence-due-date-set": "{description} due {assignmentDue}",
|
"learning.outline.sequence-due": "{description} للتسليم قبل {assignmentDue}",
|
||||||
"learning.outline.sequence-due-date-not-set": "{description}",
|
|
||||||
"progress.certificateStatus.unverifiedBody": "لتوليد شهادة، يجب عليك إكمال التحقق من هويتك. {idVerificationSupportLink}.",
|
"progress.certificateStatus.unverifiedBody": "لتوليد شهادة، يجب عليك إكمال التحقق من هويتك. {idVerificationSupportLink}.",
|
||||||
"progress.certificateStatus.downloadableBody": "اعرض إنجازاتك اليوم على لينكد إن أو ضمن سيرتك الذاتية. يمكنك تحميل شهادتك الآن و الوصول إليها في أي وقت من لوحة معلوماتك و ملفك الشخصي.",
|
"progress.certificateStatus.downloadableBody": "اعرض إنجازاتك اليوم على لينكد إن أو ضمن سيرتك الذاتية. يمكنك تحميل شهادتك الآن و الوصول إليها في أي وقت من لوحة معلوماتك و ملفك الشخصي.",
|
||||||
"courseCelebration.certificateBody.notAvailable.endDate": "تمت جدولة ظهور الدرجات النهائية و أي شهادات مكتسبة ابتداءً من {endDate}.",
|
"courseCelebration.certificateBody.notAvailable.endDate": "تمت جدولة ظهور الدرجات النهائية و أي شهادات مكتسبة ابتداءً من {endDate}.",
|
||||||
|
|||||||
@@ -114,8 +114,7 @@
|
|||||||
"learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}",
|
"learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}",
|
||||||
"learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements",
|
"learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements",
|
||||||
"learning.proctoringPanel.onboardingButtonPastDue": "Onboarding Past Due",
|
"learning.proctoringPanel.onboardingButtonPastDue": "Onboarding Past Due",
|
||||||
"learning.outline.sequence-due-date-set": "{description} due {assignmentDue}",
|
"learning.outline.sequence-due": "{description} due {assignmentDue}",
|
||||||
"learning.outline.sequence-due-date-not-set": "{description}",
|
|
||||||
"progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.",
|
"progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.",
|
||||||
"progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resumé today. You can download your certificate now and access it any time from your Dashboard and Profile.",
|
"progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resumé today. You can download your certificate now and access it any time from your Dashboard and Profile.",
|
||||||
"courseCelebration.certificateBody.notAvailable.endDate": "Final grades and any earned certificates are scheduled to be available after {endDate}.",
|
"courseCelebration.certificateBody.notAvailable.endDate": "Final grades and any earned certificates are scheduled to be available after {endDate}.",
|
||||||
|
|||||||
@@ -114,8 +114,7 @@
|
|||||||
"learning.proctoringPanel.onboardingButtonNotOpen": "Apertura de la integración: {releaseDate}",
|
"learning.proctoringPanel.onboardingButtonNotOpen": "Apertura de la integración: {releaseDate}",
|
||||||
"learning.proctoringPanel.reviewRequirementsButton": "Revisar las instrucciones y los requisitos del sistema",
|
"learning.proctoringPanel.reviewRequirementsButton": "Revisar las instrucciones y los requisitos del sistema",
|
||||||
"learning.proctoringPanel.onboardingButtonPastDue": "Inducción vencida",
|
"learning.proctoringPanel.onboardingButtonPastDue": "Inducción vencida",
|
||||||
"learning.outline.sequence-due-date-set": "Fecha límite para {description}: {assignmentDue}",
|
"learning.outline.sequence-due": "Fecha límite para {description}: {assignmentDue}",
|
||||||
"learning.outline.sequence-due-date-not-set": "{description}",
|
|
||||||
"progress.certificateStatus.unverifiedBody": "Para generar un certificado, debes completar la verificación de identidad. {idVerificationSupportLink}.",
|
"progress.certificateStatus.unverifiedBody": "Para generar un certificado, debes completar la verificación de identidad. {idVerificationSupportLink}.",
|
||||||
"progress.certificateStatus.downloadableBody": "Muestra tu logro en LinkedIn o en tu currículum. Puedes descargar tu certificado ahora y acceder a él en cualquier momento desde tu panel de estudiante y tu perfil.",
|
"progress.certificateStatus.downloadableBody": "Muestra tu logro en LinkedIn o en tu currículum. Puedes descargar tu certificado ahora y acceder a él en cualquier momento desde tu panel de estudiante y tu perfil.",
|
||||||
"courseCelebration.certificateBody.notAvailable.endDate": "Las calificaciones finales y los certificados obtenidos están programados para estar disponibles después de {endDate}.",
|
"courseCelebration.certificateBody.notAvailable.endDate": "Las calificaciones finales y los certificados obtenidos están programados para estar disponibles después de {endDate}.",
|
||||||
|
|||||||
@@ -114,8 +114,7 @@
|
|||||||
"learning.proctoringPanel.onboardingButtonNotOpen": "Ouverture de l'intégration : {releaseDate}",
|
"learning.proctoringPanel.onboardingButtonNotOpen": "Ouverture de l'intégration : {releaseDate}",
|
||||||
"learning.proctoringPanel.reviewRequirementsButton": "Examiner les instructions et la configuration système requise",
|
"learning.proctoringPanel.reviewRequirementsButton": "Examiner les instructions et la configuration système requise",
|
||||||
"learning.proctoringPanel.onboardingButtonPastDue": "Intégration en retard",
|
"learning.proctoringPanel.onboardingButtonPastDue": "Intégration en retard",
|
||||||
"learning.outline.sequence-due-date-set": "{description} due {assignmentDue}",
|
"learning.outline.sequence-due": "{description} échéance {assignmentDue}",
|
||||||
"learning.outline.sequence-due-date-not-set": "{description}",
|
|
||||||
"progress.certificateStatus.unverifiedBody": "Afin de générer une attestation, vous devez effectuer une vérification d'identité. {idVerificationSupportLink}.",
|
"progress.certificateStatus.unverifiedBody": "Afin de générer une attestation, vous devez effectuer une vérification d'identité. {idVerificationSupportLink}.",
|
||||||
"progress.certificateStatus.downloadableBody": "Présentez vos réalisations sur LinkedIn ou votre curriculum vitae aujourd'hui. Vous pouvez télécharger votre certificat maintenant et y accéder à tout moment depuis votre tableau de bord et votre profil.",
|
"progress.certificateStatus.downloadableBody": "Présentez vos réalisations sur LinkedIn ou votre curriculum vitae aujourd'hui. Vous pouvez télécharger votre certificat maintenant et y accéder à tout moment depuis votre tableau de bord et votre profil.",
|
||||||
"courseCelebration.certificateBody.notAvailable.endDate": "Les notes finales et toutes les attestations obtenues devraient être disponibles après le {endDate}.",
|
"courseCelebration.certificateBody.notAvailable.endDate": "Les notes finales et toutes les attestations obtenues devraient être disponibles après le {endDate}.",
|
||||||
|
|||||||
@@ -114,10 +114,9 @@
|
|||||||
"learning.proctoringPanel.onboardingButtonNotOpen": "Ouverture de l'intégration : {releaseDate}",
|
"learning.proctoringPanel.onboardingButtonNotOpen": "Ouverture de l'intégration : {releaseDate}",
|
||||||
"learning.proctoringPanel.reviewRequirementsButton": "Examiner les instructions et la configuration système requise",
|
"learning.proctoringPanel.reviewRequirementsButton": "Examiner les instructions et la configuration système requise",
|
||||||
"learning.proctoringPanel.onboardingButtonPastDue": "Intégration en retard",
|
"learning.proctoringPanel.onboardingButtonPastDue": "Intégration en retard",
|
||||||
"learning.outline.sequence-due-date-set": "{description} échéance {assignmentDue}",
|
"learning.outline.sequence-due": "{description} échéance {assignmentDue}",
|
||||||
"learning.outline.sequence-due-date-not-set": "{description}",
|
|
||||||
"progress.certificateStatus.unverifiedBody": "Afin de générer une attestation, vous devez effectuer une vérification d'identité. {idVerificationSupportLink}.",
|
"progress.certificateStatus.unverifiedBody": "Afin de générer une attestation, vous devez effectuer une vérification d'identité. {idVerificationSupportLink}.",
|
||||||
"progress.certificateStatus.downloadableBody": "Présentez vos réalisations sur LinkedIn ou votre curriculum vitae aujourd'hui. Vous pouvez télécharger votre attestation maintenant et y accéder à tout moment depuis votre tableau de bord et votre profil.",
|
"progress.certificateStatus.downloadableBody": "Présentez vos réalisations sur LinkedIn ou votre curriculum vitae aujourd'hui. Vous pouvez télécharger votre certificat maintenant et y accéder à tout moment depuis votre tableau de bord et votre profil.",
|
||||||
"courseCelebration.certificateBody.notAvailable.endDate": "Les notes finales et toutes les attestations obtenues devraient être disponibles après le {endDate}.",
|
"courseCelebration.certificateBody.notAvailable.endDate": "Les notes finales et toutes les attestations obtenues devraient être disponibles après le {endDate}.",
|
||||||
"progress.certificateStatus.notPassingHeader": "État de l'attestation",
|
"progress.certificateStatus.notPassingHeader": "État de l'attestation",
|
||||||
"progress.certificateStatus.notPassingBody": "Pour être admissible à une attestation, vous devez avoir la note de passage.",
|
"progress.certificateStatus.notPassingBody": "Pour être admissible à une attestation, vous devez avoir la note de passage.",
|
||||||
@@ -142,7 +141,7 @@
|
|||||||
"progress.completion.donut.label": "complété",
|
"progress.completion.donut.label": "complété",
|
||||||
"progress.completion.body": "Cela représente la part du contenu du cours que vous avez terminé. Notez que certains contenus peuvent ne pas encore être publiés.",
|
"progress.completion.body": "Cela représente la part du contenu du cours que vous avez terminé. Notez que certains contenus peuvent ne pas encore être publiés.",
|
||||||
"progress.completion.tooltip.locked": "Contenu que vous avez terminé.",
|
"progress.completion.tooltip.locked": "Contenu que vous avez terminé.",
|
||||||
"progress.completion.header": "Complétion du cours",
|
"progress.completion.header": "Achèvement du cours",
|
||||||
"progress.completion.tooltip": "Contenu auquel vous avez accès et que vous n'avez pas terminé.",
|
"progress.completion.tooltip": "Contenu auquel vous avez accès et que vous n'avez pas terminé.",
|
||||||
"progress.completion.tooltip.complete": "Contenu verrouillé et disponible uniquement pour ceux qui effectuent une mise à niveau.",
|
"progress.completion.tooltip.complete": "Contenu verrouillé et disponible uniquement pour ceux qui effectuent une mise à niveau.",
|
||||||
"progress.completion.donut.percentComplete": "Vous avez terminé {percent}% du contenu de ce cours.",
|
"progress.completion.donut.percentComplete": "Vous avez terminé {percent}% du contenu de ce cours.",
|
||||||
@@ -304,7 +303,7 @@
|
|||||||
"courseExit.nextButton.endOfCourse": "Suivant (fin du cours)",
|
"courseExit.nextButton.endOfCourse": "Suivant (fin du cours)",
|
||||||
"courseExit.profileLink": "Profil",
|
"courseExit.profileLink": "Profil",
|
||||||
"courseExit.programs.lastCourse": "Vous avez terminé le dernier cours de {title}!",
|
"courseExit.programs.lastCourse": "Vous avez terminé le dernier cours de {title}!",
|
||||||
"courseCelebration.requestCertificateBodyText": "Pour accéder à votre attestation, demandez-la ci-dessous.",
|
"courseCelebration.requestCertificateBodyText": "Pour accéder à votre certificat, demandez-le ci-dessous.",
|
||||||
"courseCelebration.requestCertificateButton": "Demander une attestation",
|
"courseCelebration.requestCertificateButton": "Demander une attestation",
|
||||||
"courseExit.searchOurCatalogLink": "Rechercher dans notre catalogue",
|
"courseExit.searchOurCatalogLink": "Rechercher dans notre catalogue",
|
||||||
"courseCelebration.shareMessage": "Partagez votre succès sur les réseaux sociaux ou par courriel.",
|
"courseCelebration.shareMessage": "Partagez votre succès sur les réseaux sociaux ou par courriel.",
|
||||||
@@ -399,7 +398,7 @@
|
|||||||
"learning.generic.upgradeNotification.pursueAverifiedCertificate": "Obtenir une attestation vérifiée",
|
"learning.generic.upgradeNotification.pursueAverifiedCertificate": "Obtenir une attestation vérifiée",
|
||||||
"learning.generic.upgradeNotification.code": "Utilisez le code {code} lors du paiement",
|
"learning.generic.upgradeNotification.code": "Utilisez le code {code} lors du paiement",
|
||||||
"learning.generic.upsell.verifiedCertBullet.verifiedCert": "attestation vérifiée",
|
"learning.generic.upsell.verifiedCertBullet.verifiedCert": "attestation vérifiée",
|
||||||
"learning.generic.upsell.verifiedCertBullet": "Obtenez une {verifiedCertLink} de complétion pour la mettre en valeur sur votre CV",
|
"learning.generic.upsell.verifiedCertBullet": "Obtenez un {verifiedCertLink} d'achèvement pour le mettre en valeur sur votre CV",
|
||||||
"learning.generic.upsell.unlockGradedBullet.gradedAssignments": "évaluations notées",
|
"learning.generic.upsell.unlockGradedBullet.gradedAssignments": "évaluations notées",
|
||||||
"learning.generic.upsell.unlockGradedBullet": "Débloquer votre accès à toutes les activités du cours, incluant {gradedAssignmentsInBoldText}",
|
"learning.generic.upsell.unlockGradedBullet": "Débloquer votre accès à toutes les activités du cours, incluant {gradedAssignmentsInBoldText}",
|
||||||
"learning.generic.upsell.fullAccessBullet.fullAccess": "Accès complet",
|
"learning.generic.upsell.fullAccessBullet.fullAccess": "Accès complet",
|
||||||
@@ -414,23 +413,23 @@
|
|||||||
"tours.existingUserTour.launchTourCheckpoint.body": "Nous avons récemment ajouté des nouvelles fonctions à l'expérience de cours. Vous avez besoin d'aide à les trouver? Prenez un tour guidé pour en apprendre plus.",
|
"tours.existingUserTour.launchTourCheckpoint.body": "Nous avons récemment ajouté des nouvelles fonctions à l'expérience de cours. Vous avez besoin d'aide à les trouver? Prenez un tour guidé pour en apprendre plus.",
|
||||||
"tours.button.dismiss": "Rejeter",
|
"tours.button.dismiss": "Rejeter",
|
||||||
"tours.button.next": "Suivant",
|
"tours.button.next": "Suivant",
|
||||||
"tours.button.okay": "D'accord",
|
"tours.button.okay": "Okay",
|
||||||
"tours.button.beginTour": "Commencer la visite guidée",
|
"tours.button.beginTour": "Commencer la visite guidée",
|
||||||
"tours.button.launchTour": "Lancer la visite guidée",
|
"tours.button.launchTour": "Lancer la visite guidée",
|
||||||
"tours.newUserModal.body": "Faisons un tour rapide de {siteName} afin que vous puissiez tirer le meilleur parti de votre cours.",
|
"tours.newUserModal.body": "Faisons un tour rapide de {siteName} afin que vous puissiez tirer le meilleur parti de votre cours.",
|
||||||
"tours.newUserModal.title.welcome": "Bienvenue à votre",
|
"tours.newUserModal.title.welcome": "Bienvenu à votre",
|
||||||
"tours.button.skipForNow": "Ignorer pour l'instant",
|
"tours.button.skipForNow": "Ignorer pour l'instant",
|
||||||
"tours.datesCheckpoint.body": "Dates importantes afin de vous maintenir sur la bonne voie.",
|
"tours.datesCheckpoint.body": "Dates importantes afin de vous maintenir sur la bonne voie.",
|
||||||
"tours.datesCheckpoint.title": "Restez au courant des dates importantes",
|
"tours.datesCheckpoint.title": "Restez au courant des dates importantes",
|
||||||
"tours.outlineCheckpoint.body": "Vous pouvez explorer les sections du cours en utilisant la table des matières ci-dessous.",
|
"tours.outlineCheckpoint.body": "Vous pouvez explorer les sections du cours en utilisant la table des matières ci-dessous.",
|
||||||
"tours.outlineCheckpoint.title": "Suivez le cours!",
|
"tours.outlineCheckpoint.title": "Suivez le cours!",
|
||||||
"tours.tabNavigationCheckpoint.body": "Ces onglets peuvent être utilisés pour accéder aux autres ressources de cours, telles que votre progression, le plan de cours, etc.",
|
"tours.tabNavigationCheckpoint.body": "Ces onglets peuvent être utilisés pour accéder aux autres ressources de cours, tel que votre progression, le plan de cours, etc.",
|
||||||
"tours.tabNavigationCheckpoint.title": "Ressources de cours additionnelles",
|
"tours.tabNavigationCheckpoint.title": "Ressources de cours additionnelles",
|
||||||
"tours.upgradeCheckpoint.body": "Travaillez vers une attestation et obtenez un accès complet au matériel de cours. Mettre à niveau maintenant!",
|
"tours.upgradeCheckpoint.body": "Travaillez vers une attestation et obtenez un accès complet au matériel de cours. Mettre à niveau maintenant!",
|
||||||
"tours.upgradeCheckpoint.title": "Débloquez votre cours",
|
"tours.upgradeCheckpoint.title": "Débloquez votre cours",
|
||||||
"tours.weeklyGoalsCheckpoint.body": "Paramétrer un objectif encourage à compléter votre cours.",
|
"tours.weeklyGoalsCheckpoint.body": "Paramétrer un objectif encourage à compléter votre cours.",
|
||||||
"tours.weeklyGoalsCheckpoint.title": "Paramétrer un objectif de cours",
|
"tours.weeklyGoalsCheckpoint.title": "Paramétrer un objectif de cours",
|
||||||
"tours.newUserModal.title": "{welcome} cours sur {siteName} !",
|
"tours.newUserModal.title": "{welcome} {siteName} au cours!",
|
||||||
"learning.effortEstimation.combinedEstimate": "{minutes} + {activities}",
|
"learning.effortEstimation.combinedEstimate": "{minutes} + {activities}",
|
||||||
"learning.effortEstimation.activities": "{activityCount, plural, one {# activité} many {# activités} other {# activités}}",
|
"learning.effortEstimation.activities": "{activityCount, plural, one {# activité} many {# activités} other {# activités}}",
|
||||||
"learning.effortEstimation.minutesAbbreviated": "{minuteCount, plural, one {# min} many {# min} other {# min}}",
|
"learning.effortEstimation.minutesAbbreviated": "{minuteCount, plural, one {# min} many {# min} other {# min}}",
|
||||||
|
|||||||
@@ -114,8 +114,7 @@
|
|||||||
"learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}",
|
"learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}",
|
||||||
"learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements",
|
"learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements",
|
||||||
"learning.proctoringPanel.onboardingButtonPastDue": "Onboarding Past Due",
|
"learning.proctoringPanel.onboardingButtonPastDue": "Onboarding Past Due",
|
||||||
"learning.outline.sequence-due-date-set": "{description} due {assignmentDue}",
|
"learning.outline.sequence-due": "{description} due {assignmentDue}",
|
||||||
"learning.outline.sequence-due-date-not-set": "{description}",
|
|
||||||
"progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.",
|
"progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.",
|
||||||
"progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resumé today. You can download your certificate now and access it any time from your Dashboard and Profile.",
|
"progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resumé today. You can download your certificate now and access it any time from your Dashboard and Profile.",
|
||||||
"courseCelebration.certificateBody.notAvailable.endDate": "Final grades and any earned certificates are scheduled to be available after {endDate}.",
|
"courseCelebration.certificateBody.notAvailable.endDate": "Final grades and any earned certificates are scheduled to be available after {endDate}.",
|
||||||
|
|||||||
@@ -114,8 +114,7 @@
|
|||||||
"learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}",
|
"learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}",
|
||||||
"learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements",
|
"learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements",
|
||||||
"learning.proctoringPanel.onboardingButtonPastDue": "Onboarding Past Due",
|
"learning.proctoringPanel.onboardingButtonPastDue": "Onboarding Past Due",
|
||||||
"learning.outline.sequence-due-date-set": "{description} due {assignmentDue}",
|
"learning.outline.sequence-due": "{description} due {assignmentDue}",
|
||||||
"learning.outline.sequence-due-date-not-set": "{description}",
|
|
||||||
"progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.",
|
"progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.",
|
||||||
"progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resumé today. You can download your certificate now and access it any time from your Dashboard and Profile.",
|
"progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resumé today. You can download your certificate now and access it any time from your Dashboard and Profile.",
|
||||||
"courseCelebration.certificateBody.notAvailable.endDate": "Final grades and any earned certificates are scheduled to be available after {endDate}.",
|
"courseCelebration.certificateBody.notAvailable.endDate": "Final grades and any earned certificates are scheduled to be available after {endDate}.",
|
||||||
|
|||||||
@@ -114,8 +114,7 @@
|
|||||||
"learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}",
|
"learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}",
|
||||||
"learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements",
|
"learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements",
|
||||||
"learning.proctoringPanel.onboardingButtonPastDue": "Onboarding Past Due",
|
"learning.proctoringPanel.onboardingButtonPastDue": "Onboarding Past Due",
|
||||||
"learning.outline.sequence-due-date-set": "{description} due {assignmentDue}",
|
"learning.outline.sequence-due": "{description} due {assignmentDue}",
|
||||||
"learning.outline.sequence-due-date-not-set": "{description}",
|
|
||||||
"progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.",
|
"progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.",
|
||||||
"progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resumé today. You can download your certificate now and access it any time from your Dashboard and Profile.",
|
"progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resumé today. You can download your certificate now and access it any time from your Dashboard and Profile.",
|
||||||
"courseCelebration.certificateBody.notAvailable.endDate": "Final grades and any earned certificates are scheduled to be available after {endDate}.",
|
"courseCelebration.certificateBody.notAvailable.endDate": "Final grades and any earned certificates are scheduled to be available after {endDate}.",
|
||||||
|
|||||||
@@ -114,8 +114,7 @@
|
|||||||
"learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}",
|
"learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}",
|
||||||
"learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements",
|
"learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements",
|
||||||
"learning.proctoringPanel.onboardingButtonPastDue": "Onboarding Past Due",
|
"learning.proctoringPanel.onboardingButtonPastDue": "Onboarding Past Due",
|
||||||
"learning.outline.sequence-due-date-set": "{description} due {assignmentDue}",
|
"learning.outline.sequence-due": "{description} due {assignmentDue}",
|
||||||
"learning.outline.sequence-due-date-not-set": "{description}",
|
|
||||||
"progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.",
|
"progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.",
|
||||||
"progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resumé today. You can download your certificate now and access it any time from your Dashboard and Profile.",
|
"progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resumé today. You can download your certificate now and access it any time from your Dashboard and Profile.",
|
||||||
"courseCelebration.certificateBody.notAvailable.endDate": "Final grades and any earned certificates are scheduled to be available after {endDate}.",
|
"courseCelebration.certificateBody.notAvailable.endDate": "Final grades and any earned certificates are scheduled to be available after {endDate}.",
|
||||||
|
|||||||
@@ -114,8 +114,7 @@
|
|||||||
"learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}",
|
"learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}",
|
||||||
"learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements",
|
"learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements",
|
||||||
"learning.proctoringPanel.onboardingButtonPastDue": "Onboarding Past Due",
|
"learning.proctoringPanel.onboardingButtonPastDue": "Onboarding Past Due",
|
||||||
"learning.outline.sequence-due-date-set": "{description} due {assignmentDue}",
|
"learning.outline.sequence-due": "{description} due {assignmentDue}",
|
||||||
"learning.outline.sequence-due-date-not-set": "{description}",
|
|
||||||
"progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.",
|
"progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.",
|
||||||
"progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resumé today. You can download your certificate now and access it any time from your Dashboard and Profile.",
|
"progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resumé today. You can download your certificate now and access it any time from your Dashboard and Profile.",
|
||||||
"courseCelebration.certificateBody.notAvailable.endDate": "Final grades and any earned certificates are scheduled to be available after {endDate}.",
|
"courseCelebration.certificateBody.notAvailable.endDate": "Final grades and any earned certificates are scheduled to be available after {endDate}.",
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
"learning.accessExpiration.header": "Audit Access Expires {date}",
|
"learning.accessExpiration.header": "Audit Access Expires {date}",
|
||||||
"learning.accessExpiration.body": "You lose all access to this course, including your progress, on {date}.",
|
"learning.accessExpiration.body": "You lose all access to this course, including your progress, on {date}.",
|
||||||
"instructorToolbar.pageBanner.courseHasExpired": "This learner no longer has access to this course. Their access expired on {date}.",
|
"instructorToolbar.pageBanner.courseHasExpired": "This learner no longer has access to this course. Their access expired on {date}.",
|
||||||
"learning.accessExpiration.upgradeNow": "马上升级",
|
"learning.accessExpiration.upgradeNow": "Upgrade now",
|
||||||
"learning.activeEnterprise.alert": " {changeActiveEnterprise}.",
|
"learning.activeEnterprise.alert": " {changeActiveEnterprise}.",
|
||||||
"learning.activeEnterprise.change.alert": "change enterprise now",
|
"learning.activeEnterprise.change.alert": "change enterprise now",
|
||||||
"learning.outline.alert.start.short": "Course starts {timeRemaining} at {courseStartTime}.",
|
"learning.outline.alert.start.short": "Course starts {timeRemaining} at {courseStartTime}.",
|
||||||
"learning.outline.alert.end.long": "This course is ending {timeRemaining} on {courseEndDate}.",
|
"learning.outline.alert.end.long": "This course is ending {timeRemaining} on {courseEndDate}.",
|
||||||
"learning.outline.alert.end.calendar": "Don’t forget to add a calendar reminder!",
|
"learning.outline.alert.end.calendar": "Don’t forget to add a calendar reminder!",
|
||||||
"instructorToolbar.pageBanner.courseHasNotStarted": "This learner does not yet have access to this course. The course starts on {date}.",
|
"instructorToolbar.pageBanner.courseHasNotStarted": "This learner does not yet have access to this course. The course starts on {date}.",
|
||||||
"learning.enrollment.alert": "您必须报读此课程才能查看课程内容。",
|
"learning.enrollment.alert": "You must be enrolled in the course to see course content.",
|
||||||
"learning.staff.enrollment.alert": "You are viewing this course as staff, and are not enrolled.",
|
"learning.staff.enrollment.alert": "You are viewing this course as staff, and are not enrolled.",
|
||||||
"learning.enrollment.enrollNow.Inline": "Enroll now",
|
"learning.enrollment.enrollNow.Inline": "Enroll now",
|
||||||
"learning.enrollment.enrollNow.Sentence": "Enroll now.",
|
"learning.enrollment.enrollNow.Sentence": "Enroll now.",
|
||||||
@@ -22,17 +22,17 @@
|
|||||||
"account-activation.alert.title": "Activate your account so you can log back in",
|
"account-activation.alert.title": "Activate your account so you can log back in",
|
||||||
"learn.sequence.entranceExamTextNotPassing": "To access course materials, you must score {entranceExamMinimumScorePct}% or higher on this exam. Your current score is {entranceExamCurrentScore}%.",
|
"learn.sequence.entranceExamTextNotPassing": "To access course materials, you must score {entranceExamMinimumScorePct}% or higher on this exam. Your current score is {entranceExamCurrentScore}%.",
|
||||||
"learn.sequence.entranceExamTextPassed": "Your score is {entranceExamCurrentScore}%. You have passed the entrance exam.",
|
"learn.sequence.entranceExamTextPassed": "Your score is {entranceExamCurrentScore}%. You have passed the entrance exam.",
|
||||||
"learning.dates.badge.completed": "已完成",
|
"learning.dates.badge.completed": "Completed",
|
||||||
"learning.dates.badge.dueNext": "Due next",
|
"learning.dates.badge.dueNext": "Due next",
|
||||||
"learning.dates.badge.pastDue": "Past due",
|
"learning.dates.badge.pastDue": "Past due",
|
||||||
"learning.dates.title": "重要日期",
|
"learning.dates.title": "Important dates",
|
||||||
"learning.dates.badge.today": "今天",
|
"learning.dates.badge.today": "Today",
|
||||||
"learning.dates.badge.unreleased": "Not yet released",
|
"learning.dates.badge.unreleased": "Not yet released",
|
||||||
"learning.dates.badge.verifiedOnly": "Verified only",
|
"learning.dates.badge.verifiedOnly": "Verified only",
|
||||||
"learning.goals.unsubscribe.contact": "请联系技术支持",
|
"learning.goals.unsubscribe.contact": "contact support",
|
||||||
"learning.goals.unsubscribe.description": "You will no longer receive email reminders about your goal for {courseTitle}.",
|
"learning.goals.unsubscribe.description": "You will no longer receive email reminders about your goal for {courseTitle}.",
|
||||||
"learning.goals.unsubscribe.errorHeader": "Something went wrong",
|
"learning.goals.unsubscribe.errorHeader": "Something went wrong",
|
||||||
"learning.goals.unsubscribe.goToDashboard": "去控制面板",
|
"learning.goals.unsubscribe.goToDashboard": "Go to dashboard",
|
||||||
"learning.goals.unsubscribe.header": "You’ve unsubscribed from goal reminders",
|
"learning.goals.unsubscribe.header": "You’ve unsubscribed from goal reminders",
|
||||||
"learning.goals.unsubscribe.loading": "Unsubscribing…",
|
"learning.goals.unsubscribe.loading": "Unsubscribing…",
|
||||||
"learning.goals.unsubscribe.errorDescription": "We were unable to unsubscribe you from goal reminder emails. Please try again later or {contactSupport} for help.",
|
"learning.goals.unsubscribe.errorDescription": "We were unable to unsubscribe you from goal reminder emails. Please try again later or {contactSupport} for help.",
|
||||||
@@ -52,21 +52,21 @@
|
|||||||
"learning.outline.goalButton.screenReader.text": "Casual",
|
"learning.outline.goalButton.screenReader.text": "Casual",
|
||||||
"learning.outline.certificateAlt": "Example Certificate",
|
"learning.outline.certificateAlt": "Example Certificate",
|
||||||
"learning.outline.collapseAll": "Collapse all",
|
"learning.outline.collapseAll": "Collapse all",
|
||||||
"learning.outline.completedAssignment": "已完成",
|
"learning.outline.completedAssignment": "Completed",
|
||||||
"learning.outline.completedSection": "Completed section",
|
"learning.outline.completedSection": "Completed section",
|
||||||
"learning.outline.dates": "重要日期",
|
"learning.outline.dates": "Important dates",
|
||||||
"learning.outline.editGoal": "Edit goal",
|
"learning.outline.editGoal": "Edit goal",
|
||||||
"learning.outline.expandAll": "Expand all",
|
"learning.outline.expandAll": "Expand all",
|
||||||
"learning.outline.goal": "Goal",
|
"learning.outline.goal": "Goal",
|
||||||
"learning.outline.goalReminderDetail": "If we notice you’re not quite at your goal, we’ll send you an email reminder.",
|
"learning.outline.goalReminderDetail": "If we notice you’re not quite at your goal, we’ll send you an email reminder.",
|
||||||
"learning.outline.goalUnsure": "再想想",
|
"learning.outline.goalUnsure": "Not sure yet",
|
||||||
"learning.outline.handouts": "课程讲义",
|
"learning.outline.handouts": "Course Handouts",
|
||||||
"learning.outline.incompleteAssignment": "未完成",
|
"learning.outline.incompleteAssignment": "Incomplete",
|
||||||
"learning.outline.incompleteSection": "Incomplete section",
|
"learning.outline.incompleteSection": "Incomplete section",
|
||||||
"learning.outline.goalButton.intense.text": "5 days a week",
|
"learning.outline.goalButton.intense.text": "5 days a week",
|
||||||
"learning.outline.goalButton.intense.title": "Intense",
|
"learning.outline.goalButton.intense.title": "Intense",
|
||||||
"learning.outline.learnMore": "了解更多",
|
"learning.outline.learnMore": "Learn More",
|
||||||
"learning.outline.altText.openSection": "打开",
|
"learning.outline.altText.openSection": "Open",
|
||||||
"learning.proctoringPanel.header": "This course contains proctored exams",
|
"learning.proctoringPanel.header": "This course contains proctored exams",
|
||||||
"learning.outline.goalButton.regular.text": "3 days a week",
|
"learning.outline.goalButton.regular.text": "3 days a week",
|
||||||
"learning.outline.goalButton.regular.title": "Regular",
|
"learning.outline.goalButton.regular.title": "Regular",
|
||||||
@@ -79,19 +79,19 @@
|
|||||||
"learning.outline.setWeeklyGoalDetail": "Setting a goal motivates you to finish the course. You can always change it later.",
|
"learning.outline.setWeeklyGoalDetail": "Setting a goal motivates you to finish the course. You can always change it later.",
|
||||||
"learning.outline.start": "Start course",
|
"learning.outline.start": "Start course",
|
||||||
"learning.outline.startBlurb": "Begin your course today",
|
"learning.outline.startBlurb": "Begin your course today",
|
||||||
"learning.outline.tools": "课程工具",
|
"learning.outline.tools": "Course Tools",
|
||||||
"learning.outline.upgradeButton": "Upgrade ({symbol}{price})",
|
"learning.outline.upgradeButton": "Upgrade ({symbol}{price})",
|
||||||
"learning.outline.upgradeTitle": "考取认证证书",
|
"learning.outline.upgradeTitle": "Pursue a verified certificate",
|
||||||
"learning.outline.welcomeMessage": "Welcome Message",
|
"learning.outline.welcomeMessage": "Welcome Message",
|
||||||
"learning.outline.welcomeMessageShowMoreButton": "显示更多",
|
"learning.outline.welcomeMessageShowMoreButton": "Show More",
|
||||||
"learning.outline.welcomeMessageShowLessButton": "查看收起",
|
"learning.outline.welcomeMessageShowLessButton": "Show Less",
|
||||||
"learning.outline.goalWelcome": "欢迎来到",
|
"learning.outline.goalWelcome": "Welcome to",
|
||||||
"learning.proctoringPanel.status.notStarted": "尚未开始",
|
"learning.proctoringPanel.status.notStarted": "Not Started",
|
||||||
"learning.proctoringPanel.status.started": "已经开始",
|
"learning.proctoringPanel.status.started": "Started",
|
||||||
"learning.proctoringPanel.status.submitted": "已提交",
|
"learning.proctoringPanel.status.submitted": "Submitted",
|
||||||
"learning.proctoringPanel.status.verified": "已经过身份认证的",
|
"learning.proctoringPanel.status.verified": "Verified",
|
||||||
"learning.proctoringPanel.status.rejected": "拒绝",
|
"learning.proctoringPanel.status.rejected": "Rejected",
|
||||||
"learning.proctoringPanel.status.error": "错误",
|
"learning.proctoringPanel.status.error": "Error",
|
||||||
"learning.proctoringPanel.status.otherCourseApproved": "Approved in Another Course",
|
"learning.proctoringPanel.status.otherCourseApproved": "Approved in Another Course",
|
||||||
"learning.proctoringPanel.status.expiringSoon": "Expiring Soon",
|
"learning.proctoringPanel.status.expiringSoon": "Expiring Soon",
|
||||||
"learning.proctoringPanel.status.expired": "Expired",
|
"learning.proctoringPanel.status.expired": "Expired",
|
||||||
@@ -114,8 +114,7 @@
|
|||||||
"learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}",
|
"learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}",
|
||||||
"learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements",
|
"learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements",
|
||||||
"learning.proctoringPanel.onboardingButtonPastDue": "Onboarding Past Due",
|
"learning.proctoringPanel.onboardingButtonPastDue": "Onboarding Past Due",
|
||||||
"learning.outline.sequence-due-date-set": "{description} due {assignmentDue}",
|
"learning.outline.sequence-due": "{description} due {assignmentDue}",
|
||||||
"learning.outline.sequence-due-date-not-set": "{description}",
|
|
||||||
"progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.",
|
"progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.",
|
||||||
"progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resumé today. You can download your certificate now and access it any time from your Dashboard and Profile.",
|
"progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resumé today. You can download your certificate now and access it any time from your Dashboard and Profile.",
|
||||||
"courseCelebration.certificateBody.notAvailable.endDate": "Final grades and any earned certificates are scheduled to be available after {endDate}.",
|
"courseCelebration.certificateBody.notAvailable.endDate": "Final grades and any earned certificates are scheduled to be available after {endDate}.",
|
||||||
@@ -133,13 +132,13 @@
|
|||||||
"progress.certificateStatus.viewableButton": "View my certificate",
|
"progress.certificateStatus.viewableButton": "View my certificate",
|
||||||
"progress.certificateStatus.notAvailableHeader": "Certificate status",
|
"progress.certificateStatus.notAvailableHeader": "Certificate status",
|
||||||
"progress.certificateBody.notAvailable.endDate": "Final grades and any earned certificates are scheduled to be available after {endDate}.",
|
"progress.certificateBody.notAvailable.endDate": "Final grades and any earned certificates are scheduled to be available after {endDate}.",
|
||||||
"progress.certificateStatus.upgradeHeader": "获取证书",
|
"progress.certificateStatus.upgradeHeader": "Earn a certificate",
|
||||||
"progress.certificateStatus.upgradeBody": "You are in an audit track and do not qualify for a certificate. In order to work towards a certificate, upgrade your course today.",
|
"progress.certificateStatus.upgradeBody": "You are in an audit track and do not qualify for a certificate. In order to work towards a certificate, upgrade your course today.",
|
||||||
"progress.certificateStatus.upgradeButton": "马上升级",
|
"progress.certificateStatus.upgradeButton": "Upgrade now",
|
||||||
"progress.certificateStatus.unverifiedHomeHeader.v2": "Verify your identity to qualify for a certificate.",
|
"progress.certificateStatus.unverifiedHomeHeader.v2": "Verify your identity to qualify for a certificate.",
|
||||||
"progress.certificateStatus.unverifiedHomeButton": "Verify my ID",
|
"progress.certificateStatus.unverifiedHomeButton": "Verify my ID",
|
||||||
"progress.certificateStatus.unverifiedHomeBody": "In order to generate a certificate for this course, you must complete the ID verification process.",
|
"progress.certificateStatus.unverifiedHomeBody": "In order to generate a certificate for this course, you must complete the ID verification process.",
|
||||||
"progress.completion.donut.label": "完成",
|
"progress.completion.donut.label": "completed",
|
||||||
"progress.completion.body": "This represents how much of the course content you have completed. Note that some content may not yet be released.",
|
"progress.completion.body": "This represents how much of the course content you have completed. Note that some content may not yet be released.",
|
||||||
"progress.completion.tooltip.locked": "Content that you have completed.",
|
"progress.completion.tooltip.locked": "Content that you have completed.",
|
||||||
"progress.completion.header": "Course completion",
|
"progress.completion.header": "Course completion",
|
||||||
@@ -151,11 +150,11 @@
|
|||||||
"progress.creditInformation.creditNotEligible": "You are no longer eligible for credit in this course. Learn more about {creditLink}.",
|
"progress.creditInformation.creditNotEligible": "You are no longer eligible for credit in this course. Learn more about {creditLink}.",
|
||||||
"progress.creditInformation.creditEligible": "\n You have met the requirements for credit in this course. Go to your\n {dashboardLink} to purchase course credit. Or learn more about {creditLink}.",
|
"progress.creditInformation.creditEligible": "\n You have met the requirements for credit in this course. Go to your\n {dashboardLink} to purchase course credit. Or learn more about {creditLink}.",
|
||||||
"progress.creditInformation.creditPartialEligible": "You have not yet met the requirements for credit. Learn more about {creditLink}.",
|
"progress.creditInformation.creditPartialEligible": "You have not yet met the requirements for credit. Learn more about {creditLink}.",
|
||||||
"progress.creditInformation.completed": "已完成",
|
"progress.creditInformation.completed": "Completed",
|
||||||
"progress.creditInformation.courseCredit": "course credit",
|
"progress.creditInformation.courseCredit": "course credit",
|
||||||
"progress.creditInformation.minimumGrade": "Minimum grade for credit ({minGrade}%)",
|
"progress.creditInformation.minimumGrade": "Minimum grade for credit ({minGrade}%)",
|
||||||
"progress.creditInformation.requirementsHeader": "Requirements for course credit",
|
"progress.creditInformation.requirementsHeader": "Requirements for course credit",
|
||||||
"progress.creditInformation.upcoming": "即将到来",
|
"progress.creditInformation.upcoming": "Upcoming",
|
||||||
"progress.creditInformation.verificationFailed": "Verification failed",
|
"progress.creditInformation.verificationFailed": "Verification failed",
|
||||||
"progress.creditInformation.verificationSubmitted": "Verification submitted",
|
"progress.creditInformation.verificationSubmitted": "Verification submitted",
|
||||||
"progress.ungradedAlert": "For progress on ungraded aspects of the course, view your {outlineLink}.",
|
"progress.ungradedAlert": "For progress on ungraded aspects of the course, view your {outlineLink}.",
|
||||||
@@ -173,18 +172,18 @@
|
|||||||
"progress.courseGrade.preview.body.unlockCertificate": "Unlock to view grades and work towards a certificate.",
|
"progress.courseGrade.preview.body.unlockCertificate": "Unlock to view grades and work towards a certificate.",
|
||||||
"progress.courseGrade.partialpreview.body.unlockCertificate": "Unlock to work towards a certificate.",
|
"progress.courseGrade.partialpreview.body.unlockCertificate": "Unlock to work towards a certificate.",
|
||||||
"progress.courseGrade.preview.body.upgradeDeadlinePassed": "The deadline to upgrade in this course has passed.",
|
"progress.courseGrade.preview.body.upgradeDeadlinePassed": "The deadline to upgrade in this course has passed.",
|
||||||
"progress.courseGrade.preview.button.upgrade": "马上升级",
|
"progress.courseGrade.preview.button.upgrade": "Upgrade now",
|
||||||
"progress.courseGrade.gradeRange.tooltip": "Grade ranges for this course:",
|
"progress.courseGrade.gradeRange.tooltip": "Grade ranges for this course:",
|
||||||
"progress.courseOutline": "课程大纲",
|
"progress.courseOutline": "Course Outline",
|
||||||
"progress.courseGrade.label.currentGrade": "Your current grade",
|
"progress.courseGrade.label.currentGrade": "Your current grade",
|
||||||
"progress.detailedGrades": "Detailed grades",
|
"progress.detailedGrades": "Detailed grades",
|
||||||
"progress.detailedGrades.emptyTable": "You currently have no graded problem scores.",
|
"progress.detailedGrades.emptyTable": "You currently have no graded problem scores.",
|
||||||
"progress.footnotes.title": "Grade summary footnotes",
|
"progress.footnotes.title": "Grade summary footnotes",
|
||||||
"progress.gradeSummary.grade": "成绩",
|
"progress.gradeSummary.grade": "Grade",
|
||||||
"progress.courseGrade.grades": "Grades",
|
"progress.courseGrade.grades": "Grades",
|
||||||
"progress.courseGrade.gradesAndCredit": "Grades & Credit",
|
"progress.courseGrade.gradesAndCredit": "Grades & Credit",
|
||||||
"progress.courseGrade.gradeRange.Tooltip": "Grade range tooltip",
|
"progress.courseGrade.gradeRange.Tooltip": "Grade range tooltip",
|
||||||
"progress.gradeSummary": "评分汇总",
|
"progress.gradeSummary": "Grade summary",
|
||||||
"progress.gradeSummary.limitedAccessExplanation": "You have limited access to graded assignments as part of the audit track in this course.",
|
"progress.gradeSummary.limitedAccessExplanation": "You have limited access to graded assignments as part of the audit track in this course.",
|
||||||
"progress.gradeSummary.tooltip.alt": "Grade summary tooltip",
|
"progress.gradeSummary.tooltip.alt": "Grade summary tooltip",
|
||||||
"progress.gradeSummary.tooltip.body": "Your course assignment's weight is determined by your instructor. By multiplying your grade by the weight for that assignment type, your weighted grade is calculated. Your weighted grade is what's used to determine if you pass the course.",
|
"progress.gradeSummary.tooltip.body": "Your course assignment's weight is determined by your instructor. By multiplying your grade by the weight for that assignment type, your weighted grade is calculated. Your weighted grade is what's used to determine if you pass the course.",
|
||||||
@@ -193,33 +192,33 @@
|
|||||||
"progress.courseGrade.label.passingGrade": "Passing grade",
|
"progress.courseGrade.label.passingGrade": "Passing grade",
|
||||||
"progress.detailedGrades.problemScore.label": "Problem Scores:",
|
"progress.detailedGrades.problemScore.label": "Problem Scores:",
|
||||||
"progress.detailedGrades.problemScore.toggleButton": "Toggle individual problem scores for {subsectionTitle}",
|
"progress.detailedGrades.problemScore.toggleButton": "Toggle individual problem scores for {subsectionTitle}",
|
||||||
"progress.detailedGrades.overridden": "这部分的成绩已经被修订。",
|
"progress.detailedGrades.overridden": "Section grade has been overridden.",
|
||||||
"progress.score": "分数",
|
"progress.score": "Score",
|
||||||
"progress.weight": "权重",
|
"progress.weight": "Weight",
|
||||||
"progress.weightedGrade": "Weighted grade",
|
"progress.weightedGrade": "Weighted grade",
|
||||||
"progress.weightedGradeSummary": "Your current weighted grade summary",
|
"progress.weightedGradeSummary": "Your current weighted grade summary",
|
||||||
"progress.header": "Your progress",
|
"progress.header": "Your progress",
|
||||||
"progress.header.targetUser": "Course progress for {username}",
|
"progress.header.targetUser": "Course progress for {username}",
|
||||||
"progress.link.studio": "View grading in Studio",
|
"progress.link.studio": "View grading in Studio",
|
||||||
"progress.relatedLinks.datesCard.description": "A schedule view of your course due dates and upcoming assignments.",
|
"progress.relatedLinks.datesCard.description": "A schedule view of your course due dates and upcoming assignments.",
|
||||||
"progress.relatedLinks.datesCard.link": "日期",
|
"progress.relatedLinks.datesCard.link": "Dates",
|
||||||
"progress.relatedLinks.outlineCard.description": "A birds-eye view of your course content.",
|
"progress.relatedLinks.outlineCard.description": "A birds-eye view of your course content.",
|
||||||
"progress.relatedLinks.outlineCard.link": "课程大纲",
|
"progress.relatedLinks.outlineCard.link": "Course Outline",
|
||||||
"progress.relatedLinks": "Related links",
|
"progress.relatedLinks": "Related links",
|
||||||
"datesBanner.suggestedSchedule": "We’ve built a suggested schedule to help you stay on track. But don’t worry—it’s flexible so you can learn at your own pace.",
|
"datesBanner.suggestedSchedule": "We’ve built a suggested schedule to help you stay on track. But don’t worry—it’s flexible so you can learn at your own pace.",
|
||||||
"datesBanner.upgradeToCompleteGradedBanner.header": "升级解锁",
|
"datesBanner.upgradeToCompleteGradedBanner.header": "Upgrade to unlock",
|
||||||
"datesBanner.upgradeToCompleteGradedBanner.body": "You are auditing this course, which means that you are unable to participate in graded assignments. To complete graded assignments as part of this course, you can upgrade today.",
|
"datesBanner.upgradeToCompleteGradedBanner.body": "You are auditing this course, which means that you are unable to participate in graded assignments. To complete graded assignments as part of this course, you can upgrade today.",
|
||||||
"datesBanner.upgradeToCompleteGradedBanner.button": "马上升级",
|
"datesBanner.upgradeToCompleteGradedBanner.button": "Upgrade now",
|
||||||
"datesBanner.upgradeToResetBanner.body": "To keep yourself on track, you can update this schedule and shift the past due assignments into the future. Don’t worry—you won’t lose any of the progress you’ve made when you shift your due dates.",
|
"datesBanner.upgradeToResetBanner.body": "To keep yourself on track, you can update this schedule and shift the past due assignments into the future. Don’t worry—you won’t lose any of the progress you’ve made when you shift your due dates.",
|
||||||
"datesBanner.upgradeToResetBanner.button": "Upgrade to shift due dates",
|
"datesBanner.upgradeToResetBanner.button": "Upgrade to shift due dates",
|
||||||
"datesBanner.resetDatesBanner.header": "It looks like you missed some important deadlines based on our suggested schedule.",
|
"datesBanner.resetDatesBanner.header": "It looks like you missed some important deadlines based on our suggested schedule.",
|
||||||
"datesBanner.resetDatesBanner.body": "To keep yourself on track, you can update this schedule and shift the past due assignments into the future. Don’t worry—you won’t lose any of the progress you’ve made when you shift your due dates.",
|
"datesBanner.resetDatesBanner.body": "To keep yourself on track, you can update this schedule and shift the past due assignments into the future. Don’t worry—you won’t lose any of the progress you’ve made when you shift your due dates.",
|
||||||
"datesBanner.resetDatesBanner.button": "Shift due dates",
|
"datesBanner.resetDatesBanner.button": "Shift due dates",
|
||||||
"learn.navigation.course.tabs.label": "课程资料",
|
"learn.navigation.course.tabs.label": "Course Material",
|
||||||
"unit.bookmark.button.add.bookmark": "收藏此页",
|
"unit.bookmark.button.add.bookmark": "Bookmark this page",
|
||||||
"unit.bookmark.button.remove.bookmark": "已收藏",
|
"unit.bookmark.button.remove.bookmark": "Bookmarked",
|
||||||
"learning.celebration.completed": "You just completed the first section of your course.",
|
"learning.celebration.completed": "You just completed the first section of your course.",
|
||||||
"learning.celebration.congrats": "恭喜!",
|
"learning.celebration.congrats": "Congratulations!",
|
||||||
"learning.celebration.earned": "You earned it!",
|
"learning.celebration.earned": "You earned it!",
|
||||||
"learning.celebration.emailSubject": "I'm on my way to completing {title} online with {platform}!",
|
"learning.celebration.emailSubject": "I'm on my way to completing {title} online with {platform}!",
|
||||||
"learning.celebration.forward": "Keep going",
|
"learning.celebration.forward": "Keep going",
|
||||||
@@ -231,38 +230,38 @@
|
|||||||
"learning.celebration.setGoal": "Setting a goal can help you {strongText} in your course.",
|
"learning.celebration.setGoal": "Setting a goal can help you {strongText} in your course.",
|
||||||
"calculator.instructions.button.label": "Calculator Instructions",
|
"calculator.instructions.button.label": "Calculator Instructions",
|
||||||
"calculator.instructions": "For detailed information, see the {expressions_link}.",
|
"calculator.instructions": "For detailed information, see the {expressions_link}.",
|
||||||
"calculator.instructions.support.title": "帮助中心",
|
"calculator.instructions.support.title": "Help Center",
|
||||||
"calculator.instructions.useful.tips": "Useful tips:",
|
"calculator.instructions.useful.tips": "Useful tips:",
|
||||||
"calculator.hint1": "使用括号()使表达式更明确。您可以在括号内使用括号。",
|
"calculator.hint1": "Use parentheses () to make expressions clear. You can use parentheses inside other parentheses.",
|
||||||
"calculator.hint2": "不要在表达式中使用空格。",
|
"calculator.hint2": "Do not use spaces in expressions.",
|
||||||
"calculator.hint3": "对于常数,明确表明乘法(例如:5*c)。",
|
"calculator.hint3": "For constants, indicate multiplication explicitly (example: 5*c).",
|
||||||
"calculator.hint4": "对于词缀,输入数字和词组,不带空格(例:5c)。",
|
"calculator.hint4": "For affixes, type the number and affix without a space (example: 5c).",
|
||||||
"calculator.hint5": "对于函数,输入函数的名字,然后在括号中输入表达式。",
|
"calculator.hint5": "For functions, type the name of the function, then the expression in parentheses.",
|
||||||
"calculator.instruction.table.to.use.heading": "来使用",
|
"calculator.instruction.table.to.use.heading": "To Use",
|
||||||
"calculator.instruction.table.type.heading": "类型",
|
"calculator.instruction.table.type.heading": "Type",
|
||||||
"calculator.instruction.table.examples.heading": "例子",
|
"calculator.instruction.table.examples.heading": "Examples",
|
||||||
"calculator.instruction.table.to.use.numbers": "数字",
|
"calculator.instruction.table.to.use.numbers": "Numbers",
|
||||||
"calculator.instruction.table.to.use.numbers.type1": "整数",
|
"calculator.instruction.table.to.use.numbers.type1": "Integers",
|
||||||
"calculator.instruction.table.to.use.numbers.type2": "分数",
|
"calculator.instruction.table.to.use.numbers.type2": "Fractions",
|
||||||
"calculator.instruction.table.to.use.numbers.type3": "小数",
|
"calculator.instruction.table.to.use.numbers.type3": "Decimals",
|
||||||
"calculator.instruction.table.to.use.operators": "操作",
|
"calculator.instruction.table.to.use.operators": "Operators",
|
||||||
"calculator.instruction.table.to.use.operators.type1": "(add, subtract, multiply, divide)",
|
"calculator.instruction.table.to.use.operators.type1": "(add, subtract, multiply, divide)",
|
||||||
"calculator.instruction.table.to.use.operators.type2": "(raise to a power)",
|
"calculator.instruction.table.to.use.operators.type2": "(raise to a power)",
|
||||||
"calculator.instruction.table.to.use.operators.type3": "(parallel resistors)",
|
"calculator.instruction.table.to.use.operators.type3": "(parallel resistors)",
|
||||||
"calculator.instruction.table.to.use.constants": "常量",
|
"calculator.instruction.table.to.use.constants": "Constants",
|
||||||
"calculator.instruction.table.to.use.affixes": "词缀",
|
"calculator.instruction.table.to.use.affixes": "Affixes",
|
||||||
"calculator.instruction.table.to.use.affixes.type": "Percent sign (%)",
|
"calculator.instruction.table.to.use.affixes.type": "Percent sign (%)",
|
||||||
"calculator.instruction.table.to.use.basic.functions": "基本功能",
|
"calculator.instruction.table.to.use.basic.functions": "Basic functions",
|
||||||
"calculator.instruction.table.to.use.trig.functions": "三角函数",
|
"calculator.instruction.table.to.use.trig.functions": "Trigonometric functions",
|
||||||
"calculator.instruction.table.to.use.scientific.notation": "科学记数法",
|
"calculator.instruction.table.to.use.scientific.notation": "Scientific notation",
|
||||||
"calculator.instruction.table.to.use.scientific.notation.type1": "{exponentSyntax} and the exponent",
|
"calculator.instruction.table.to.use.scientific.notation.type1": "{exponentSyntax} and the exponent",
|
||||||
"calculator.instruction.table.to.use.scientific.notation.type2": "{notationSyntax} notation",
|
"calculator.instruction.table.to.use.scientific.notation.type2": "{notationSyntax} notation",
|
||||||
"calculator.instruction.table.to.use.scientific.notation.type3": "{notationSyntax} and the exponent",
|
"calculator.instruction.table.to.use.scientific.notation.type3": "{notationSyntax} and the exponent",
|
||||||
"calculator.button.label": "计算器",
|
"calculator.button.label": "Calculator",
|
||||||
"calculator.input.field.label": "计算器输入",
|
"calculator.input.field.label": "Calculator Input",
|
||||||
"calculator.submit.button.label": "计算",
|
"calculator.submit.button.label": "Calculate",
|
||||||
"calculator.result.field.label": "Calculator Result",
|
"calculator.result.field.label": "Calculator Result",
|
||||||
"calculator.result.field.placeholder": "结果",
|
"calculator.result.field.placeholder": "Result",
|
||||||
"notes.button.show": "Show Notes",
|
"notes.button.show": "Show Notes",
|
||||||
"notes.button.hide": "Hide Notes",
|
"notes.button.hide": "Hide Notes",
|
||||||
"courseExit.catalogSearchSuggestion": "Looking to learn more? {searchOurCatalogLink} to find more courses and programs to explore.",
|
"courseExit.catalogSearchSuggestion": "Looking to learn more? {searchOurCatalogLink} to find more courses and programs to explore.",
|
||||||
@@ -272,7 +271,7 @@
|
|||||||
"courseCelebration.certificateBody.upgradable": "It’s not too late to upgrade. For {price} you will unlock access to all graded\n assignments in this course. Upon completion, you will receive a verified certificate which is a\n valuable credential to improve your job prospects and advance your career, or highlight your\n certificate in school applications.",
|
"courseCelebration.certificateBody.upgradable": "It’s not too late to upgrade. For {price} you will unlock access to all graded\n assignments in this course. Upon completion, you will receive a verified certificate which is a\n valuable credential to improve your job prospects and advance your career, or highlight your\n certificate in school applications.",
|
||||||
"courseCelebration.upgradeDiscountCodePrompt": "Use code {code} at checkout for {percent}% off!",
|
"courseCelebration.upgradeDiscountCodePrompt": "Use code {code} at checkout for {percent}% off!",
|
||||||
"courseCelebration.recommendations.heading": "Keep building your skills with these courses!",
|
"courseCelebration.recommendations.heading": "Keep building your skills with these courses!",
|
||||||
"courseCelebration.recommendations.label": "课程",
|
"courseCelebration.recommendations.label": "Course",
|
||||||
"courseCelebration.recommendations.formatting.list_join": "{style, select, punctuation {, } conjunction { {sp}and } other { }}",
|
"courseCelebration.recommendations.formatting.list_join": "{style, select, punctuation {, } conjunction { {sp}and } other { }}",
|
||||||
"courseCelebration.recommendations.browse_catalog": "Explore more courses",
|
"courseCelebration.recommendations.browse_catalog": "Explore more courses",
|
||||||
"courseCelebration.recommendations.loading_recommendations": "Loading recommendations",
|
"courseCelebration.recommendations.loading_recommendations": "Loading recommendations",
|
||||||
@@ -283,33 +282,33 @@
|
|||||||
"courseCelebration.certificateHeader.notAvailable": "Your grade and certificate status will be available soon.",
|
"courseCelebration.certificateHeader.notAvailable": "Your grade and certificate status will be available soon.",
|
||||||
"courseCelebration.certificateBody.notAvailable.accessCertificate": "If you have earned a passing grade, your certificate will be automatically issued.",
|
"courseCelebration.certificateBody.notAvailable.accessCertificate": "If you have earned a passing grade, your certificate will be automatically issued.",
|
||||||
"courseCelebration.certificateHeader.unverified": "You must complete verification to receive your certificate.",
|
"courseCelebration.certificateHeader.unverified": "You must complete verification to receive your certificate.",
|
||||||
"courseCelebration.certificateHeader.requestable": "恭喜!您已具备获得证书的资格!",
|
"courseCelebration.certificateHeader.requestable": "Congratulations, you qualified for a certificate!",
|
||||||
"courseCelebration.certificateHeader.upgradable": "Upgrade to pursue a verified certificate",
|
"courseCelebration.certificateHeader.upgradable": "Upgrade to pursue a verified certificate",
|
||||||
"courseCelebration.certificateImage": "Sample certificate",
|
"courseCelebration.certificateImage": "Sample certificate",
|
||||||
"courseCelebration.completedCourseHeader": "You have completed your course.",
|
"courseCelebration.completedCourseHeader": "You have completed your course.",
|
||||||
"courseCelebration.congratulationsHeader": "恭喜!",
|
"courseCelebration.congratulationsHeader": "Congratulations!",
|
||||||
"courseCelebration.congratulationsImage": "Four people raising their hands in celebration",
|
"courseCelebration.congratulationsImage": "Four people raising their hands in celebration",
|
||||||
"courseExit.courseInProgressDescription": "It looks like there is more content in this course that will be released in the future. Look out for email updates or check back on your course for when this content will be available.",
|
"courseExit.courseInProgressDescription": "It looks like there is more content in this course that will be released in the future. Look out for email updates or check back on your course for when this content will be available.",
|
||||||
"courseExit.courseInProgressHeader": "More content is coming soon!",
|
"courseExit.courseInProgressHeader": "More content is coming soon!",
|
||||||
"courseExit.dashboardLink": "课程面板",
|
"courseExit.dashboardLink": "Dashboard",
|
||||||
"courseExit.endOfCourseDescription": "Unfortunately, you are not currently eligible for a certificate. You need to receive a passing grade to be eligible for a certificate.",
|
"courseExit.endOfCourseDescription": "Unfortunately, you are not currently eligible for a certificate. You need to receive a passing grade to be eligible for a certificate.",
|
||||||
"courseExit.endOfCourseHeader": "You’ve reached the end of the course!",
|
"courseExit.endOfCourseHeader": "You’ve reached the end of the course!",
|
||||||
"courseExit.endOfCourseTitle": "End of Course",
|
"courseExit.endOfCourseTitle": "End of Course",
|
||||||
"courseExit.idVerificationSupportLink": "Learn more about ID verification",
|
"courseExit.idVerificationSupportLink": "Learn more about ID verification",
|
||||||
"courseCelebration.linkedinAddToProfileButton": "添加至LinkedIn用户资料中",
|
"courseCelebration.linkedinAddToProfileButton": "Add to LinkedIn profile",
|
||||||
"courseExit.programs.microBachelors.learnMore": "Learn more about how your MicroBachelors credential can be applied for credit.",
|
"courseExit.programs.microBachelors.learnMore": "Learn more about how your MicroBachelors credential can be applied for credit.",
|
||||||
"courseExit.programs.microMasters.learnMore": "Learn more about the process of applying MicroMasters certificates to Master’s degrees.",
|
"courseExit.programs.microMasters.learnMore": "Learn more about the process of applying MicroMasters certificates to Master’s degrees.",
|
||||||
"courseExit.programs.microMasters.mastersMessage": "If you’re interested in using your MicroMasters certificate towards a Master’s program, you can get started today!",
|
"courseExit.programs.microMasters.mastersMessage": "If you’re interested in using your MicroMasters certificate towards a Master’s program, you can get started today!",
|
||||||
"learn.sequence.navigation.complete.button": "完成课程",
|
"learn.sequence.navigation.complete.button": "Complete the course",
|
||||||
"courseExit.nextButton.endOfCourse": "Next (end of course)",
|
"courseExit.nextButton.endOfCourse": "Next (end of course)",
|
||||||
"courseExit.profileLink": "个人主页",
|
"courseExit.profileLink": "Profile",
|
||||||
"courseExit.programs.lastCourse": "You have completed the last course in {title}!",
|
"courseExit.programs.lastCourse": "You have completed the last course in {title}!",
|
||||||
"courseCelebration.requestCertificateBodyText": "In order to access your certificate, request it below.",
|
"courseCelebration.requestCertificateBodyText": "In order to access your certificate, request it below.",
|
||||||
"courseCelebration.requestCertificateButton": "Request certificate",
|
"courseCelebration.requestCertificateButton": "Request certificate",
|
||||||
"courseExit.searchOurCatalogLink": "Search our catalog",
|
"courseExit.searchOurCatalogLink": "Search our catalog",
|
||||||
"courseCelebration.shareMessage": "Share your success on social media or email.",
|
"courseCelebration.shareMessage": "Share your success on social media or email.",
|
||||||
"courseExit.social.shareCompletionMessage": "I just completed {title} with {platform}!",
|
"courseExit.social.shareCompletionMessage": "I just completed {title} with {platform}!",
|
||||||
"courseExit.upgradeButton": "马上升级",
|
"courseExit.upgradeButton": "Upgrade now",
|
||||||
"courseExit.upgradeLink": "upgrade now",
|
"courseExit.upgradeLink": "upgrade now",
|
||||||
"courseCelebration.verificationPending": "Your ID verification is pending and your certificate will be available once approved.",
|
"courseCelebration.verificationPending": "Your ID verification is pending and your certificate will be available once approved.",
|
||||||
"courseExit.verifiedCertificateSupportLink": "Learn more about verified certificates",
|
"courseExit.verifiedCertificateSupportLink": "Learn more about verified certificates",
|
||||||
@@ -320,31 +319,31 @@
|
|||||||
"courseExit.viewGradesButton": "View grades",
|
"courseExit.viewGradesButton": "View grades",
|
||||||
"courseExit.programCompletion.dashboardMessage": "To view your certificate status, check the Programs section of your {programLink}.",
|
"courseExit.programCompletion.dashboardMessage": "To view your certificate status, check the Programs section of your {programLink}.",
|
||||||
"courseExit.upgradeFootnote": "Access to this course and its materials are available on your dashboard until {expirationDate}. To extend access, {upgradeLink}.",
|
"courseExit.upgradeFootnote": "Access to this course and its materials are available on your dashboard until {expirationDate}. To extend access, {upgradeLink}.",
|
||||||
"learn.course.license.allRightsReserved.text": "保留所有权利",
|
"learn.course.license.allRightsReserved.text": "All Rights Reserved",
|
||||||
"learn.course.license.creativeCommons.terms.preamble": "Creative Commons licensed content, with terms as follows:",
|
"learn.course.license.creativeCommons.terms.preamble": "Creative Commons licensed content, with terms as follows:",
|
||||||
"learn.course.license.creativeCommons.terms.by": "署名",
|
"learn.course.license.creativeCommons.terms.by": "Attribution",
|
||||||
"learn.course.license.creativeCommons.terms.nc": "非营利性的",
|
"learn.course.license.creativeCommons.terms.nc": "Noncommercial",
|
||||||
"learn.course.license.creativeCommons.terms.nd": "无相关衍生作品",
|
"learn.course.license.creativeCommons.terms.nd": "No Derivatives",
|
||||||
"learn.course.license.creativeCommons.terms.sa": "分享",
|
"learn.course.license.creativeCommons.terms.sa": "Share Alike",
|
||||||
"learn.course.license.creativeCommons.terms.zero": "No terms",
|
"learn.course.license.creativeCommons.terms.zero": "No terms",
|
||||||
"learn.course.license.creativeCommons.text": "保留部分权利",
|
"learn.course.license.creativeCommons.text": "Some Rights Reserved",
|
||||||
"learn.breadcrumb.navigation.course.home": "课程",
|
"learn.breadcrumb.navigation.course.home": "Course",
|
||||||
"notification.tray.container": "Notification tray",
|
"notification.tray.container": "Notification tray",
|
||||||
"notification.open.button": "Show notification tray",
|
"notification.open.button": "Show notification tray",
|
||||||
"notification.close.button": "Close notification tray",
|
"notification.close.button": "Close notification tray",
|
||||||
"responsive.close.notification": "Back to course",
|
"responsive.close.notification": "Back to course",
|
||||||
"notification.tray.title": "通知",
|
"notification.tray.title": "Notifications",
|
||||||
"notification.tray.no.message": "You have no new notifications at this time.",
|
"notification.tray.no.message": "You have no new notifications at this time.",
|
||||||
"learn.contentLock.content.locked": "未解锁内容",
|
"learn.contentLock.content.locked": "Content Locked",
|
||||||
"learn.contentLock.complete.prerequisite": "You must complete the prerequisite: ''{prereqSectionName}'' to access this content.",
|
"learn.contentLock.complete.prerequisite": "You must complete the prerequisite: ''{prereqSectionName}'' to access this content.",
|
||||||
"learn.contentLock.goToSection": "Go To Prerequisite Section",
|
"learn.contentLock.goToSection": "Go To Prerequisite Section",
|
||||||
"learn.hiddenAfterDue.gradeAvailable": "If you have completed this assignment, your grade is available on the {progressPage}.",
|
"learn.hiddenAfterDue.gradeAvailable": "If you have completed this assignment, your grade is available on the {progressPage}.",
|
||||||
"learn.hiddenAfterDue.header": "本次作业已经过了截止日期。",
|
"learn.hiddenAfterDue.header": "The due date for this assignment has passed.",
|
||||||
"learn.hiddenAfterDue.description": "Because the due date has passed, this assignment is no longer available.",
|
"learn.hiddenAfterDue.description": "Because the due date has passed, this assignment is no longer available.",
|
||||||
"learn.hiddenAfterDue.progressPage": "progress page",
|
"learn.hiddenAfterDue.progressPage": "progress page",
|
||||||
"learn.honorCode.content": "Honesty and academic integrity are important to {siteName} and the institutions providing courses and programs on the {siteName} site. By clicking “I agree” below, I confirm that I have read, understand, and will abide by the {link} for the {siteName} Site.",
|
"learn.honorCode.content": "Honesty and academic integrity are important to {siteName} and the institutions providing courses and programs on the {siteName} site. By clicking “I agree” below, I confirm that I have read, understand, and will abide by the {link} for the {siteName} Site.",
|
||||||
"learn.honorCode.name": "诚信准则",
|
"learn.honorCode.name": "Honor Code",
|
||||||
"learn.honorCode.cancel": "取消",
|
"learn.honorCode.cancel": "Cancel",
|
||||||
"learn.honorCode.agree": "I agree",
|
"learn.honorCode.agree": "I agree",
|
||||||
"learn.lockPaywall.title": "Graded assignments are locked",
|
"learn.lockPaywall.title": "Graded assignments are locked",
|
||||||
"learn.lockPaywall.content": "Upgrade to gain access to locked features like this one and get the most out of your course.",
|
"learn.lockPaywall.content": "Upgrade to gain access to locked features like this one and get the most out of your course.",
|
||||||
@@ -358,25 +357,25 @@
|
|||||||
"learn.loading.content.lock": "Loading locked content messaging...",
|
"learn.loading.content.lock": "Loading locked content messaging...",
|
||||||
"learn.loading.learning.sequence": "Loading learning sequence...",
|
"learn.loading.learning.sequence": "Loading learning sequence...",
|
||||||
"learn.sequence.no.content": "There is no content here.",
|
"learn.sequence.no.content": "There is no content here.",
|
||||||
"learn.sequence.navigation.next.button": "下一节",
|
"learn.sequence.navigation.next.button": "Next",
|
||||||
"learn.sequence.navigation.next.up.button": "Next Up: {title}",
|
"learn.sequence.navigation.next.up.button": "Next Up: {title}",
|
||||||
"learn.sequence.navigation.previous.button": "上一节",
|
"learn.sequence.navigation.previous.button": "Previous",
|
||||||
"learn.course.sequence.navigation.mobile.menu": "{current} of {total}",
|
"learn.course.sequence.navigation.mobile.menu": "{current} of {total}",
|
||||||
"learn.sequence.share.button": "Share this content",
|
"learn.sequence.share.button": "Share this content",
|
||||||
"learn.sequence.share.modal.title": "标题",
|
"learn.sequence.share.modal.title": "Title",
|
||||||
"learn.sequence.share.modal.body": "Copy the link below to share this content.",
|
"learn.sequence.share.modal.body": "Copy the link below to share this content.",
|
||||||
"learn.sequence.share.quote": "Here's a fun clip from a class I'm taking on @edXonline.\n",
|
"learn.sequence.share.quote": "Here's a fun clip from a class I'm taking on @edXonline.\n",
|
||||||
"discussions.sidebar.title": "讨论",
|
"discussions.sidebar.title": "Discussions",
|
||||||
"discussions.sidebar.open.button": "Show discussions tray",
|
"discussions.sidebar.open.button": "Show discussions tray",
|
||||||
"learn.redirect.interstitial.message": "Redirecting...",
|
"learn.redirect.interstitial.message": "Redirecting...",
|
||||||
"learn.loading.error": "错误: {error}",
|
"learn.loading.error": "Error: {error}",
|
||||||
"learning.celebration.emailBody": "What are you spending your time learning?",
|
"learning.celebration.emailBody": "What are you spending your time learning?",
|
||||||
"learning.social.shareEmail": "Share your progress via email.",
|
"learning.social.shareEmail": "Share your progress via email.",
|
||||||
"learning.social.shareService": "Share your progress on {service}.",
|
"learning.social.shareService": "Share your progress on {service}.",
|
||||||
"general.altText.close": "关闭",
|
"general.altText.close": "Close",
|
||||||
"learning.logistration.register": "注册",
|
"learning.logistration.register": "register",
|
||||||
"learning.logistration.login": "登录",
|
"learning.logistration.login": "sign in",
|
||||||
"general.signIn.sentenceCase": "登录",
|
"general.signIn.sentenceCase": "Sign in",
|
||||||
"learn.course.tabs.navigation.overflow.menu": "More...",
|
"learn.course.tabs.navigation.overflow.menu": "More...",
|
||||||
"learning.offer.screenReaderPrices": "Original price: {originalPrice}, discount price: {discountedPrice}",
|
"learning.offer.screenReaderPrices": "Original price: {originalPrice}, discount price: {discountedPrice}",
|
||||||
"learning.upgradeButton.screenReaderInlinePrices": "Original price: {originalPrice}",
|
"learning.upgradeButton.screenReaderInlinePrices": "Original price: {originalPrice}",
|
||||||
@@ -396,7 +395,7 @@
|
|||||||
"learning.generic.upgradeNotification.accessExpiration": "Upgrade your course today",
|
"learning.generic.upgradeNotification.accessExpiration": "Upgrade your course today",
|
||||||
"learning.generic.upgradeNotification.accessExpirationUrgent": "Course Access Expiration",
|
"learning.generic.upgradeNotification.accessExpirationUrgent": "Course Access Expiration",
|
||||||
"learning.generic.upgradeNotification.accessExpirationPast": "Course Access Expiration",
|
"learning.generic.upgradeNotification.accessExpirationPast": "Course Access Expiration",
|
||||||
"learning.generic.upgradeNotification.pursueAverifiedCertificate": "获取认证证书",
|
"learning.generic.upgradeNotification.pursueAverifiedCertificate": "Pursue a verified certificate",
|
||||||
"learning.generic.upgradeNotification.code": "Use code {code} at checkout",
|
"learning.generic.upgradeNotification.code": "Use code {code} at checkout",
|
||||||
"learning.generic.upsell.verifiedCertBullet.verifiedCert": "verified certificate",
|
"learning.generic.upsell.verifiedCertBullet.verifiedCert": "verified certificate",
|
||||||
"learning.generic.upsell.verifiedCertBullet": "Earn a {verifiedCertLink} of completion to showcase on your resumé",
|
"learning.generic.upsell.verifiedCertBullet": "Earn a {verifiedCertLink} of completion to showcase on your resumé",
|
||||||
@@ -412,8 +411,8 @@
|
|||||||
"tours.abandonTour.launchTourCheckpoint.body": "Feeling lost? Launch the tour any time for some quick tips to get the most out of the experience.",
|
"tours.abandonTour.launchTourCheckpoint.body": "Feeling lost? Launch the tour any time for some quick tips to get the most out of the experience.",
|
||||||
"tours.sequenceNavigationCheckpoint.body": "The top bar within your course allows you to easily jump to different sections and shows you what’s coming up.",
|
"tours.sequenceNavigationCheckpoint.body": "The top bar within your course allows you to easily jump to different sections and shows you what’s coming up.",
|
||||||
"tours.existingUserTour.launchTourCheckpoint.body": "We’ve recently added a few new features to the course experience. Want some help looking around? Take a tour to learn more.",
|
"tours.existingUserTour.launchTourCheckpoint.body": "We’ve recently added a few new features to the course experience. Want some help looking around? Take a tour to learn more.",
|
||||||
"tours.button.dismiss": "忽略",
|
"tours.button.dismiss": "Dismiss",
|
||||||
"tours.button.next": "下一节",
|
"tours.button.next": "Next",
|
||||||
"tours.button.okay": "Okay",
|
"tours.button.okay": "Okay",
|
||||||
"tours.button.beginTour": "Begin tour",
|
"tours.button.beginTour": "Begin tour",
|
||||||
"tours.button.launchTour": "Launch tour",
|
"tours.button.launchTour": "Launch tour",
|
||||||
@@ -435,7 +434,7 @@
|
|||||||
"learning.effortEstimation.activities": "{activityCount, plural, one {# activity} other {# activities}}",
|
"learning.effortEstimation.activities": "{activityCount, plural, one {# activity} other {# activities}}",
|
||||||
"learning.effortEstimation.minutesAbbreviated": "{minuteCount, plural, one {# min} other {# min}}",
|
"learning.effortEstimation.minutesAbbreviated": "{minuteCount, plural, one {# min} other {# min}}",
|
||||||
"learning.effortEstimation.minutesFull": "{minuteCount, plural, one {# minute} other {# minutes}}",
|
"learning.effortEstimation.minutesFull": "{minuteCount, plural, one {# minute} other {# minutes}}",
|
||||||
"learning.streakCelebration.congratulations": "恭喜!",
|
"learning.streakCelebration.congratulations": "Congratulations!",
|
||||||
"learning.streakCelebration.body": "Keep it up, you’re on a roll!",
|
"learning.streakCelebration.body": "Keep it up, you’re on a roll!",
|
||||||
"learning.streakCelebration.button": "Keep it up",
|
"learning.streakCelebration.button": "Keep it up",
|
||||||
"learning.streakCelebration.buttonSrOnly": "Close modal and continue",
|
"learning.streakCelebration.buttonSrOnly": "Close modal and continue",
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import NoticesProvider from './generic/notices';
|
|||||||
import PathFixesProvider from './generic/path-fixes';
|
import PathFixesProvider from './generic/path-fixes';
|
||||||
import LiveTab from './course-home/live-tab/LiveTab';
|
import LiveTab from './course-home/live-tab/LiveTab';
|
||||||
import CourseAccessErrorPage from './generic/CourseAccessErrorPage';
|
import CourseAccessErrorPage from './generic/CourseAccessErrorPage';
|
||||||
import DecodePageRoute from './decode-page-route';
|
|
||||||
|
|
||||||
subscribe(APP_READY, () => {
|
subscribe(APP_READY, () => {
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
@@ -51,28 +50,28 @@ subscribe(APP_READY, () => {
|
|||||||
<Switch>
|
<Switch>
|
||||||
<PageRoute exact path="/goal-unsubscribe/:token" component={GoalUnsubscribe} />
|
<PageRoute exact path="/goal-unsubscribe/:token" component={GoalUnsubscribe} />
|
||||||
<PageRoute path="/redirect" component={CoursewareRedirectLandingPage} />
|
<PageRoute path="/redirect" component={CoursewareRedirectLandingPage} />
|
||||||
<DecodePageRoute path="/course/:courseId/access-denied" component={CourseAccessErrorPage} />
|
<PageRoute path="/course/:courseId/access-denied" component={CourseAccessErrorPage} />
|
||||||
<DecodePageRoute path="/course/:courseId/home">
|
<PageRoute path="/course/:courseId/home">
|
||||||
<TabContainer tab="outline" fetch={fetchOutlineTab} slice="courseHome">
|
<TabContainer tab="outline" fetch={fetchOutlineTab} slice="courseHome">
|
||||||
<OutlineTab />
|
<OutlineTab />
|
||||||
</TabContainer>
|
</TabContainer>
|
||||||
</DecodePageRoute>
|
</PageRoute>
|
||||||
<DecodePageRoute path="/course/:courseId/live">
|
<PageRoute path="/course/:courseId/live">
|
||||||
<TabContainer tab="lti_live" fetch={fetchLiveTab} slice="courseHome">
|
<TabContainer tab="live" fetch={fetchLiveTab} slice="courseHome">
|
||||||
<LiveTab />
|
<LiveTab />
|
||||||
</TabContainer>
|
</TabContainer>
|
||||||
</DecodePageRoute>
|
</PageRoute>
|
||||||
<DecodePageRoute path="/course/:courseId/dates">
|
<PageRoute path="/course/:courseId/dates">
|
||||||
<TabContainer tab="dates" fetch={fetchDatesTab} slice="courseHome">
|
<TabContainer tab="dates" fetch={fetchDatesTab} slice="courseHome">
|
||||||
<DatesTab />
|
<DatesTab />
|
||||||
</TabContainer>
|
</TabContainer>
|
||||||
</DecodePageRoute>
|
</PageRoute>
|
||||||
<DecodePageRoute path="/course/:courseId/discussion/:path*">
|
<PageRoute path="/course/:courseId/discussion/:path*">
|
||||||
<TabContainer tab="discussion" fetch={fetchDiscussionTab} slice="courseHome">
|
<TabContainer tab="discussion" fetch={fetchDiscussionTab} slice="courseHome">
|
||||||
<DiscussionTab />
|
<DiscussionTab />
|
||||||
</TabContainer>
|
</TabContainer>
|
||||||
</DecodePageRoute>
|
</PageRoute>
|
||||||
<DecodePageRoute
|
<PageRoute
|
||||||
path={[
|
path={[
|
||||||
'/course/:courseId/progress/:targetUserId/',
|
'/course/:courseId/progress/:targetUserId/',
|
||||||
'/course/:courseId/progress',
|
'/course/:courseId/progress',
|
||||||
@@ -87,12 +86,12 @@ subscribe(APP_READY, () => {
|
|||||||
</TabContainer>
|
</TabContainer>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<DecodePageRoute path="/course/:courseId/course-end">
|
<PageRoute path="/course/:courseId/course-end">
|
||||||
<TabContainer tab="courseware" fetch={fetchCourse} slice="courseware">
|
<TabContainer tab="courseware" fetch={fetchCourse} slice="courseware">
|
||||||
<CourseExit />
|
<CourseExit />
|
||||||
</TabContainer>
|
</TabContainer>
|
||||||
</DecodePageRoute>
|
</PageRoute>
|
||||||
<DecodePageRoute
|
<PageRoute
|
||||||
path={[
|
path={[
|
||||||
'/course/:courseId/:sequenceId/:unitId',
|
'/course/:courseId/:sequenceId/:unitId',
|
||||||
'/course/:courseId/:sequenceId',
|
'/course/:courseId/:sequenceId',
|
||||||
@@ -136,9 +135,6 @@ initialize({
|
|||||||
TWITTER_HASHTAG: process.env.TWITTER_HASHTAG || null,
|
TWITTER_HASHTAG: process.env.TWITTER_HASHTAG || null,
|
||||||
TWITTER_URL: process.env.TWITTER_URL || null,
|
TWITTER_URL: process.env.TWITTER_URL || null,
|
||||||
LEGACY_THEME_NAME: process.env.LEGACY_THEME_NAME || null,
|
LEGACY_THEME_NAME: process.env.LEGACY_THEME_NAME || null,
|
||||||
EXAMS_BASE_URL: process.env.EXAMS_BASE_URL || null,
|
|
||||||
PROCTORED_EXAM_FAQ_URL: process.env.PROCTORED_EXAM_FAQ_URL || null,
|
|
||||||
PROCTORED_EXAM_RULES_URL: process.env.PROCTORED_EXAM_RULES_URL || null,
|
|
||||||
}, 'LearnerAppConfig');
|
}, 'LearnerAppConfig');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ describe('Courseware Tour', () => {
|
|||||||
// Wait for the page spinner to be removed, such that we can wait for our main
|
// Wait for the page spinner to be removed, such that we can wait for our main
|
||||||
// content to load before making any assertions.
|
// content to load before making any assertions.
|
||||||
await waitForElementToBeRemoved(screen.getByRole('status'));
|
await waitForElementToBeRemoved(screen.getByRole('status'));
|
||||||
return Promise.resolve(container);
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('when receiving successful course data', () => {
|
describe('when receiving successful course data', () => {
|
||||||
|
|||||||
@@ -137,12 +137,10 @@ export async function initializeTestStore(options = {}, overrideStore = true) {
|
|||||||
const discussionConfigUrl = new RegExp(`${getConfig().LMS_BASE_URL}/api/discussion/v1/courses/*`);
|
const discussionConfigUrl = new RegExp(`${getConfig().LMS_BASE_URL}/api/discussion/v1/courses/*`);
|
||||||
courseHomeMetadataUrl = appendBrowserTimezoneToUrl(courseHomeMetadataUrl);
|
courseHomeMetadataUrl = appendBrowserTimezoneToUrl(courseHomeMetadataUrl);
|
||||||
|
|
||||||
const provider = options?.provider || 'legacy';
|
|
||||||
|
|
||||||
axiosMock.onGet(courseMetadataUrl).reply(200, courseMetadata);
|
axiosMock.onGet(courseMetadataUrl).reply(200, courseMetadata);
|
||||||
axiosMock.onGet(courseHomeMetadataUrl).reply(200, courseHomeMetadata);
|
axiosMock.onGet(courseHomeMetadataUrl).reply(200, courseHomeMetadata);
|
||||||
axiosMock.onGet(learningSequencesUrlRegExp).reply(200, buildOutlineFromBlocks(courseBlocks));
|
axiosMock.onGet(learningSequencesUrlRegExp).reply(200, buildOutlineFromBlocks(courseBlocks));
|
||||||
axiosMock.onGet(discussionConfigUrl).reply(200, { provider });
|
axiosMock.onGet(discussionConfigUrl).reply(200, { provider: 'legacy' });
|
||||||
sequenceMetadata.forEach(metadata => {
|
sequenceMetadata.forEach(metadata => {
|
||||||
const sequenceMetadataUrl = `${getConfig().LMS_BASE_URL}/api/courseware/sequence/${metadata.item_id}`;
|
const sequenceMetadataUrl = `${getConfig().LMS_BASE_URL}/api/courseware/sequence/${metadata.item_id}`;
|
||||||
axiosMock.onGet(sequenceMetadataUrl).reply(200, metadata);
|
axiosMock.onGet(sequenceMetadataUrl).reply(200, metadata);
|
||||||
|
|||||||
Reference in New Issue
Block a user