Compare commits
1 Commits
open-relea
...
schen/cour
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0834baee05 |
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
|
||||||
|
|||||||
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
|
||||||
|
|||||||
40626
package-lock.json
generated
40626
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@@ -30,10 +30,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@edx/brand": "npm:@edx/brand-openedx@1.2.0",
|
"@edx/brand": "npm:@edx/brand-openedx@1.2.0",
|
||||||
"@edx/frontend-component-footer": "^12.0.0",
|
"@edx/frontend-component-footer": "11.6.3",
|
||||||
"@edx/frontend-component-header": "^4.0.0",
|
"@edx/frontend-component-header": "3.6.4",
|
||||||
"@edx/frontend-lib-special-exams": "^2.16.1",
|
"@edx/frontend-lib-special-exams": "~2.8.0",
|
||||||
"@edx/frontend-platform": "^4.2.0",
|
"@edx/frontend-platform": "3.4.1",
|
||||||
"@edx/paragon": "20.28.4",
|
"@edx/paragon": "20.28.4",
|
||||||
"@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",
|
||||||
@@ -64,7 +64,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 +74,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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -420,3 +420,10 @@ export async function unsubscribeFromCourseGoal(token) {
|
|||||||
return getAuthenticatedHttpClient().post(url.href)
|
return getAuthenticatedHttpClient().post(url.href)
|
||||||
.then(res => camelCaseObject(res));
|
.then(res => camelCaseObject(res));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function searchCourseContentFromAPI(courseId, searchKeyword) {
|
||||||
|
const url = new URL(`${getConfig().LMS_BASE_URL}/search/${courseId}`);
|
||||||
|
const formData = `search_string=${searchKeyword}&page_size=20&page_index=0`;
|
||||||
|
return getAuthenticatedHttpClient().post(url.href, formData)
|
||||||
|
.then(res => camelCaseObject(res));
|
||||||
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
postDismissWelcomeMessage,
|
postDismissWelcomeMessage,
|
||||||
postRequestCert,
|
postRequestCert,
|
||||||
getLiveTabIframe,
|
getLiveTabIframe,
|
||||||
|
searchCourseContentFromAPI,
|
||||||
} from './api';
|
} from './api';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -139,3 +140,18 @@ export function processEvent(eventData, getTabData) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function searchCourseContent(courseId, searchKeyword) {
|
||||||
|
return async (dispatch) => {
|
||||||
|
searchCourseContentFromAPI(courseId, searchKeyword).then(response => {
|
||||||
|
const { data } = response;
|
||||||
|
dispatch(addModel({
|
||||||
|
modelType: 'contentSearchResults',
|
||||||
|
model: {
|
||||||
|
id: courseId,
|
||||||
|
...data,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { useModel } from '../../generic/model-store';
|
|||||||
import WelcomeMessage from './widgets/WelcomeMessage';
|
import WelcomeMessage from './widgets/WelcomeMessage';
|
||||||
import ProctoringInfoPanel from './widgets/ProctoringInfoPanel';
|
import ProctoringInfoPanel from './widgets/ProctoringInfoPanel';
|
||||||
import AccountActivationAlert from '../../alerts/logistration-alert/AccountActivationAlert';
|
import AccountActivationAlert from '../../alerts/logistration-alert/AccountActivationAlert';
|
||||||
|
import CoursewareSearch from './widgets/CoursewareSearch';
|
||||||
|
|
||||||
const OutlineTab = ({ intl }) => {
|
const OutlineTab = ({ intl }) => {
|
||||||
const {
|
const {
|
||||||
@@ -157,6 +158,7 @@ const OutlineTab = ({ intl }) => {
|
|||||||
)}
|
)}
|
||||||
<StartOrResumeCourseCard />
|
<StartOrResumeCourseCard />
|
||||||
<WelcomeMessage courseId={courseId} />
|
<WelcomeMessage courseId={courseId} />
|
||||||
|
<CoursewareSearch courseId={courseId} />
|
||||||
{rootCourseId && (
|
{rootCourseId && (
|
||||||
<>
|
<>
|
||||||
<div className="row w-100 m-0 mb-3 justify-content-end">
|
<div className="row w-100 m-0 mb-3 justify-content-end">
|
||||||
|
|||||||
@@ -119,11 +119,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 () => {
|
||||||
|
|||||||
@@ -331,6 +331,16 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Onboarding Past Due',
|
defaultMessage: 'Onboarding Past Due',
|
||||||
description: 'Text that show when the deadline of proctortrack onboarding exam has passed, it appears on button that start the onboarding exam however for this case the button is disabled for obvious reason',
|
description: 'Text that show when the deadline of proctortrack onboarding exam has passed, it appears on button that start the onboarding exam however for this case the button is disabled for obvious reason',
|
||||||
},
|
},
|
||||||
|
coursewareSearchInputLabel: {
|
||||||
|
id: 'learning.coursewareSearch.inputLabel',
|
||||||
|
defaultMessage: 'Course Content Search',
|
||||||
|
description: 'Search input label',
|
||||||
|
},
|
||||||
|
coursewareSearchButtonLabel: {
|
||||||
|
id: 'learning.coursewareSearch.buttonLabel',
|
||||||
|
defaultMessage: 'Search',
|
||||||
|
description: 'Search button label',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default messages;
|
export default messages;
|
||||||
|
|||||||
109
src/course-home/outline-tab/widgets/CoursewareSearch.jsx
Normal file
109
src/course-home/outline-tab/widgets/CoursewareSearch.jsx
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Form,
|
||||||
|
Container,
|
||||||
|
Hyperlink,
|
||||||
|
Layout,
|
||||||
|
} from '@edx/paragon';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { sendTrackingLogEvent } from '@edx/frontend-platform/analytics';
|
||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import messages from '../messages';
|
||||||
|
import { useModel, updateModel } from '../../../generic/model-store';
|
||||||
|
import { searchCourseContent } from '../../data/thunks';
|
||||||
|
|
||||||
|
const CoursewareSearch = ({ courseId, intl }) => {
|
||||||
|
const {
|
||||||
|
org,
|
||||||
|
} = useModel('courseHomeMeta', courseId);
|
||||||
|
|
||||||
|
const eventProperties = {
|
||||||
|
org_key: org,
|
||||||
|
courserun_key: courseId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const {
|
||||||
|
results,
|
||||||
|
took,
|
||||||
|
} = useModel('contentSearchResults', courseId);
|
||||||
|
|
||||||
|
const [searchKeyword, setSearchKeyword] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!searchKeyword) {
|
||||||
|
dispatch(updateModel({
|
||||||
|
modelType: 'contentSearchResults',
|
||||||
|
model: {
|
||||||
|
id: courseId,
|
||||||
|
results: [],
|
||||||
|
took: false,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [searchKeyword]);
|
||||||
|
|
||||||
|
const searchClick = () => {
|
||||||
|
sendTrackingLogEvent('edx.course.home.courseware_search.clicked', {
|
||||||
|
...eventProperties,
|
||||||
|
event_type: 'search',
|
||||||
|
keyword: searchKeyword,
|
||||||
|
});
|
||||||
|
dispatch(searchCourseContent(courseId, searchKeyword));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Control
|
||||||
|
className="float-left w-75"
|
||||||
|
floatingLabel={intl.formatMessage(messages.coursewareSearchInputLabel)}
|
||||||
|
value={searchKeyword}
|
||||||
|
onChange={(e) => setSearchKeyword(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
className="float-left"
|
||||||
|
onClick={() => searchClick()}
|
||||||
|
>
|
||||||
|
{intl.formatMessage(messages.coursewareSearchButtonLabel)}
|
||||||
|
</Button>
|
||||||
|
<div className="clearfix" />
|
||||||
|
</Form.Group>
|
||||||
|
{(took && results.length === 0) && (
|
||||||
|
<Container size="xl">
|
||||||
|
{
|
||||||
|
`Could not find any component matching "${searchKeyword}"`
|
||||||
|
}
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
|
{(took && results.length > 0) && results.map(resultItem => (
|
||||||
|
<Container
|
||||||
|
size="xl"
|
||||||
|
>
|
||||||
|
<Layout>
|
||||||
|
<Layout.Element>
|
||||||
|
<Hyperlink
|
||||||
|
destination={`${getConfig().LMS_BASE_URL}${resultItem.data.url}`}
|
||||||
|
>
|
||||||
|
{ resultItem.data.location.join('/') }
|
||||||
|
</Hyperlink>
|
||||||
|
</Layout.Element>
|
||||||
|
</Layout>
|
||||||
|
</Container>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
CoursewareSearch.propTypes = {
|
||||||
|
courseId: PropTypes.string.isRequired,
|
||||||
|
intl: intlShape.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default injectIntl(CoursewareSearch);
|
||||||
@@ -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];
|
||||||
|
|||||||
@@ -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}/`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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' };
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user