Compare commits
13 Commits
open-relea
...
jenkins/ve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d7d95e490 | ||
|
|
0a90024de9 | ||
|
|
91d06e9788 | ||
|
|
74423bf359 | ||
|
|
7e9eab24b0 | ||
|
|
91dd10917f | ||
|
|
b2098be114 | ||
|
|
64ac98c310 | ||
|
|
8a80e2a70e | ||
|
|
a936d970db | ||
|
|
56c6c88638 | ||
|
|
9c42bfbd8a | ||
|
|
69733f7837 |
13
.github/workflows/lockfileversion-check.yml
vendored
Normal file
13
.github/workflows/lockfileversion-check.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
#check package-lock file version
|
||||
|
||||
name: Lockfile Version check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
version-check:
|
||||
uses: edx/.github/.github/workflows/lockfileversion-check.yml@master
|
||||
@@ -14,4 +14,5 @@ module.exports = createConfig('jest', {
|
||||
'src/postcss.config.js',
|
||||
],
|
||||
testTimeout: 120000,
|
||||
testEnvironment: 'jsdom',
|
||||
});
|
||||
|
||||
32628
package-lock.json
generated
32628
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@@ -28,7 +28,8 @@
|
||||
"dependencies": {
|
||||
"@edx/brand": "npm:@edx/brand-edx.org@^2.0.3",
|
||||
"@edx/frontend-component-footer": "10.1.6",
|
||||
"@edx/frontend-platform": "1.12.4",
|
||||
"@edx/frontend-component-header": "^2.4.6",
|
||||
"@edx/frontend-platform": "^1.15.6",
|
||||
"@edx/paragon": "16.14.4",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.15.4",
|
||||
@@ -51,11 +52,10 @@
|
||||
"history": "5.0.1",
|
||||
"html-react-parser": "^1.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"node-sass": "^6.0.1",
|
||||
"prop-types": "15.7.2",
|
||||
"query-string": "7.0.1",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react": "^16.14.0",
|
||||
"react-dom": "^16.14.0",
|
||||
"react-intl": "^5.20.9",
|
||||
"react-pdf": "^5.5.0",
|
||||
"react-redux": "^7.2.4",
|
||||
@@ -73,7 +73,7 @@
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@edx/frontend-build": "9.1.1",
|
||||
"@edx/frontend-build": "^9.1.4",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^12.1.0",
|
||||
"axios-mock-adapter": "^1.20.0",
|
||||
@@ -86,7 +86,7 @@
|
||||
"jest": "27.0.6",
|
||||
"jest-expect-message": "^1.0.2",
|
||||
"react-dev-utils": "^11.0.4",
|
||||
"react-test-renderer": "^17.0.2",
|
||||
"react-test-renderer": "^16.14.0",
|
||||
"reactifex": "1.1.1",
|
||||
"redux-mock-store": "^1.5.4",
|
||||
"semantic-release": "^17.4.5"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us" dir="ltr">
|
||||
<head>
|
||||
<title>ORA Enhanced Staff Grader | <%= process.env.SITE_NAME %></title>
|
||||
<title>ORA staff grading | <%= process.env.SITE_NAME %></title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon" />
|
||||
|
||||
@@ -4,11 +4,11 @@ import { connect } from 'react-redux';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
|
||||
import Footer from '@edx/frontend-component-footer';
|
||||
import { LearningHeader as Header } from '@edx/frontend-component-header';
|
||||
|
||||
import { selectors } from 'data/redux';
|
||||
|
||||
import DemoWarning from 'containers/DemoWarning';
|
||||
import CourseHeader from 'containers/CourseHeader';
|
||||
import ListView from 'containers/ListView';
|
||||
|
||||
import './App.scss';
|
||||
@@ -16,7 +16,7 @@ import './App.scss';
|
||||
export const App = ({ courseMetadata, isEnabled }) => (
|
||||
<Router>
|
||||
<div>
|
||||
<CourseHeader
|
||||
<Header
|
||||
courseTitle={courseMetadata.title}
|
||||
courseNumber={courseMetadata.number}
|
||||
courseOrg={courseMetadata.org}
|
||||
|
||||
26
src/App.scss
26
src/App.scss
@@ -42,32 +42,6 @@ $input-focus-box-shadow: $input-box-shadow; // hack to get upgrade to paragon 4.
|
||||
}
|
||||
}
|
||||
|
||||
.course-header {
|
||||
min-width: 0;
|
||||
border-bottom: 1px solid black;
|
||||
|
||||
.course-title-lockup {
|
||||
min-width: 0;
|
||||
|
||||
span {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-bottom: 0.1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.user-dropdown {
|
||||
.btn {
|
||||
height: 3rem;
|
||||
@media (max-width: -1 + map-get($grid-breakpoints, "sm")) {
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#paragon-portal-root {
|
||||
.pgn__modal-layer {
|
||||
.pgn__modal-close-container {
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import Footer from '@edx/frontend-component-footer';
|
||||
import { LearningHeader as Header } from '@edx/frontend-component-header';
|
||||
|
||||
import ListView from 'containers/ListView';
|
||||
|
||||
@@ -16,11 +17,13 @@ jest.mock('data/redux', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-component-header', () => ({
|
||||
LearningHeader: 'Header',
|
||||
}));
|
||||
jest.mock('@edx/frontend-component-footer', () => 'Footer');
|
||||
|
||||
jest.mock('containers/DemoWarning', () => 'DemoWarning');
|
||||
jest.mock('containers/ListView', () => 'ListView');
|
||||
jest.mock('containers/CourseHeader', () => 'CourseHeader');
|
||||
|
||||
const logo = 'fakeLogo.png';
|
||||
let el;
|
||||
@@ -57,5 +60,16 @@ describe('App router component', () => {
|
||||
test('Footer logo drawn from env variable', () => {
|
||||
expect(router.find(Footer).props().logo).toEqual(logo);
|
||||
});
|
||||
|
||||
test('Header to use courseMetadata props', () => {
|
||||
const {
|
||||
courseTitle,
|
||||
courseNumber,
|
||||
courseOrg,
|
||||
} = router.find(Header).props();
|
||||
expect(courseTitle).toEqual(props.courseMetadata.title);
|
||||
expect(courseNumber).toEqual(props.courseMetadata.number);
|
||||
expect(courseOrg).toEqual(props.courseMetadata.org);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`App router component snapshot: disabled (show demo warning) 1`] = `
|
||||
<BrowserRouter>
|
||||
<div>
|
||||
<CourseHeader
|
||||
<Header
|
||||
courseNumber="course-number"
|
||||
courseOrg="course-org"
|
||||
courseTitle="course-title"
|
||||
@@ -22,7 +22,7 @@ exports[`App router component snapshot: disabled (show demo warning) 1`] = `
|
||||
exports[`App router component snapshot: enabled 1`] = `
|
||||
<BrowserRouter>
|
||||
<div>
|
||||
<CourseHeader
|
||||
<Header
|
||||
courseNumber="course-number"
|
||||
courseOrg="course-org"
|
||||
courseTitle="course-title"
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
Icon, Form, ActionRow, IconButton,
|
||||
} from '@edx/paragon';
|
||||
import { ChevronLeft, ChevronRight } from '@edx/paragon/icons';
|
||||
import pdfjsWorker from 'react-pdf/node_modules/pdfjs-dist/build/pdf.worker.entry';
|
||||
import pdfjsWorker from 'react-pdf/dist/esm/pdf.worker.entry';
|
||||
|
||||
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getLoginRedirectUrl } from '@edx/frontend-platform/auth';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Button } from '@edx/paragon';
|
||||
|
||||
import message from './messages';
|
||||
|
||||
export const getRegisterUrl = () => {
|
||||
const { LMS_BASE_URL } = getConfig();
|
||||
const locationHref = encodeURIComponent(global.location.href);
|
||||
return `${LMS_BASE_URL}/register?next=${locationHref}`;
|
||||
};
|
||||
|
||||
export const AnonymousUserMenu = ({ intl }) => (
|
||||
<div>
|
||||
<Button
|
||||
className="mr-3"
|
||||
variant="outline-primary"
|
||||
href={getRegisterUrl()}
|
||||
>
|
||||
{intl.formatMessage(message.registerSentenceCase)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
href={`${getLoginRedirectUrl(global.location.href)}`}
|
||||
>
|
||||
{intl.formatMessage(message.signInSentenceCase)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
AnonymousUserMenu.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(AnonymousUserMenu);
|
||||
@@ -1,24 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { AnonymousUserMenu } from './AnonymousUserMenu';
|
||||
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
getConfig: () => ({
|
||||
LMS_BASE_URL: '<LMS_BASE_URL>',
|
||||
}),
|
||||
}));
|
||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
getLoginRedirectUrl: (url) => `redirect:${url}`,
|
||||
}));
|
||||
|
||||
describe('Header AnonymousUserMenu component', () => {
|
||||
const props = {
|
||||
intl: { formatMessage: (msg) => msg.defaultMessage },
|
||||
};
|
||||
test('snapshot', () => {
|
||||
expect(
|
||||
shallow(<AnonymousUserMenu {...props} />),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faUserCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { Dropdown } from '@edx/paragon';
|
||||
|
||||
export const UserAvatar = ({ username }) => (
|
||||
<Dropdown.Toggle variant="outline-primary">
|
||||
<FontAwesomeIcon
|
||||
icon={faUserCircle}
|
||||
className="d-md-none"
|
||||
size="lg"
|
||||
/>
|
||||
<span data-hj-suppress className="d-none d-md-inline">
|
||||
{username}
|
||||
</span>
|
||||
</Dropdown.Toggle>
|
||||
);
|
||||
UserAvatar.propTypes = {
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
UserAvatar.defaultProps = {};
|
||||
|
||||
export default UserAvatar;
|
||||
@@ -1,23 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import UserAvatar from './UserAvatar';
|
||||
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
getConfig: () => ({
|
||||
LMS_BASE_URL: '<LMS_BASE_URL>',
|
||||
LOGOUT_URL: '<LOGOUT_URL>',
|
||||
SUPPORT_URL: '<SUPPORT_URL>',
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('Header AuthenticatedUserDropdown UserAvatar component', () => {
|
||||
const props = {
|
||||
username: 'test-username',
|
||||
};
|
||||
test('snapshot', () => {
|
||||
expect(
|
||||
shallow(<UserAvatar {...props} />),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,40 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Dropdown } from '@edx/paragon';
|
||||
|
||||
import messages from '../messages';
|
||||
|
||||
export class UserMenu extends React.Component {
|
||||
menuItem(href, message) {
|
||||
return (
|
||||
<Dropdown.Item href={href}>
|
||||
{this.props.intl.formatMessage(message)}
|
||||
</Dropdown.Item>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { username } = this.props;
|
||||
const { LMS_BASE_URL, LOGOUT_URL } = getConfig();
|
||||
return (
|
||||
<Dropdown.Menu className="dropdown-menu-right">
|
||||
{this.menuItem(`${LMS_BASE_URL}/dashboard`, messages.dashboard)}
|
||||
{this.menuItem(`${LMS_BASE_URL}/u/${username}`, messages.profile)}
|
||||
{this.menuItem(`${LMS_BASE_URL}/account/settings`, messages.account)}
|
||||
{this.menuItem(LOGOUT_URL, messages.signOut)}
|
||||
</Dropdown.Menu>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UserMenu.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
UserMenu.defaultProps = {};
|
||||
|
||||
export default injectIntl(UserMenu);
|
||||
@@ -1,24 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { UserMenu } from './UserMenu';
|
||||
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
getConfig: () => ({
|
||||
LMS_BASE_URL: '<LMS_BASE_URL>',
|
||||
LOGOUT_URL: '<LOGOUT_URL>',
|
||||
SUPPORT_URL: '<SUPPORT_URL>',
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('Header AuthenticatedUserDropdown UserMenu component', () => {
|
||||
const props = {
|
||||
intl: { formatMessage: (msg) => msg.defaultMessage },
|
||||
username: 'test-username',
|
||||
};
|
||||
test('snapshot', () => {
|
||||
expect(
|
||||
shallow(<UserMenu {...props} />),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,31 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Header AuthenticatedUserDropdown UserAvatar component snapshot 1`] = `
|
||||
<Dropdown.Toggle
|
||||
variant="outline-primary"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className="d-md-none"
|
||||
icon={
|
||||
Object {
|
||||
"icon": Array [
|
||||
496,
|
||||
512,
|
||||
Array [],
|
||||
"f2bd",
|
||||
"M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 96c48.6 0 88 39.4 88 88s-39.4 88-88 88-88-39.4-88-88 39.4-88 88-88zm0 344c-58.7 0-111.3-26.6-146.5-68.2 18.8-35.4 55.6-59.8 98.5-59.8 2.4 0 4.8.4 7.1 1.1 13 4.2 26.6 6.9 40.9 6.9 14.3 0 28-2.7 40.9-6.9 2.3-.7 4.7-1.1 7.1-1.1 42.9 0 79.7 24.4 98.5 59.8C359.3 421.4 306.7 448 248 448z",
|
||||
],
|
||||
"iconName": "user-circle",
|
||||
"prefix": "fas",
|
||||
}
|
||||
}
|
||||
size="lg"
|
||||
/>
|
||||
<span
|
||||
className="d-none d-md-inline"
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
test-username
|
||||
</span>
|
||||
</Dropdown.Toggle>
|
||||
`;
|
||||
@@ -1,28 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Header AuthenticatedUserDropdown UserMenu component snapshot 1`] = `
|
||||
<Dropdown.Menu
|
||||
className="dropdown-menu-right"
|
||||
>
|
||||
<Dropdown.Item
|
||||
href="<LMS_BASE_URL>/dashboard"
|
||||
>
|
||||
Dashboard
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
href="<LMS_BASE_URL>/u/test-username"
|
||||
>
|
||||
Profile
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
href="<LMS_BASE_URL>/account/settings"
|
||||
>
|
||||
Account
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
href="<LOGOUT_URL>"
|
||||
>
|
||||
Sign Out
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
`;
|
||||
@@ -1,22 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Header AuthenticatedUserDropdown component snapshot 1`] = `
|
||||
<Fragment>
|
||||
<a
|
||||
className="text-gray-700 mr-3"
|
||||
href="<SUPPORT_URL>"
|
||||
>
|
||||
Help
|
||||
</a>
|
||||
<Dropdown
|
||||
className="user-dropdown"
|
||||
>
|
||||
<UserAvatar
|
||||
username="test-username"
|
||||
/>
|
||||
<UserMenu
|
||||
username="test-username"
|
||||
/>
|
||||
</Dropdown>
|
||||
</Fragment>
|
||||
`;
|
||||
@@ -1,35 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Dropdown } from '@edx/paragon';
|
||||
|
||||
import UserMenu from './UserMenu';
|
||||
import UserAvatar from './UserAvatar';
|
||||
|
||||
import messages from '../messages';
|
||||
|
||||
export const AuthenticatedUserDropdown = ({
|
||||
intl,
|
||||
username,
|
||||
}) => (
|
||||
<>
|
||||
<a className="text-gray-700 mr-3" href={`${getConfig().SUPPORT_URL}`}>
|
||||
{intl.formatMessage(messages.help)}
|
||||
</a>
|
||||
<Dropdown className="user-dropdown">
|
||||
<UserAvatar username={username} />
|
||||
<UserMenu username={username} />
|
||||
</Dropdown>
|
||||
</>
|
||||
);
|
||||
|
||||
AuthenticatedUserDropdown.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
AuthenticatedUserDropdown.defaultProps = {};
|
||||
|
||||
export default injectIntl(AuthenticatedUserDropdown);
|
||||
@@ -1,24 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { AuthenticatedUserDropdown } from '.';
|
||||
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
getConfig: () => ({
|
||||
SUPPORT_URL: '<SUPPORT_URL>',
|
||||
}),
|
||||
}));
|
||||
jest.mock('./UserAvatar', () => 'UserAvatar');
|
||||
jest.mock('./UserMenu', () => 'UserMenu');
|
||||
|
||||
describe('Header AuthenticatedUserDropdown component', () => {
|
||||
const props = {
|
||||
intl: { formatMessage: (msg) => msg.defaultMessage },
|
||||
username: 'test-username',
|
||||
};
|
||||
test('snapshot', () => {
|
||||
expect(
|
||||
shallow(<AuthenticatedUserDropdown {...props} />),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const CourseLabel = ({
|
||||
courseOrg,
|
||||
courseNumber,
|
||||
courseTitle,
|
||||
}) => (
|
||||
<div
|
||||
className="flex-grow-1 course-title-lockup"
|
||||
style={{ lineHeight: 1 }}
|
||||
>
|
||||
<span className="d-block small m-0">
|
||||
{courseOrg} {courseNumber}
|
||||
</span>
|
||||
<span className="d-block m-0 font-weight-bold course-title">
|
||||
{courseTitle}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
CourseLabel.propTypes = {
|
||||
courseOrg: PropTypes.string,
|
||||
courseNumber: PropTypes.string,
|
||||
courseTitle: PropTypes.string,
|
||||
};
|
||||
CourseLabel.defaultProps = {
|
||||
courseOrg: null,
|
||||
courseNumber: null,
|
||||
courseTitle: null,
|
||||
};
|
||||
|
||||
export default CourseLabel;
|
||||
@@ -1,18 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import CourseLabel from './CourseLabel';
|
||||
|
||||
const courseData = {
|
||||
courseOrg: 'course-org',
|
||||
courseNumber: 'course-number',
|
||||
courseTitle: 'course-title',
|
||||
};
|
||||
|
||||
describe('Header CourseLabel component', () => {
|
||||
test('snapshot', () => {
|
||||
expect(
|
||||
shallow(<CourseLabel {...courseData} />),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
import React from 'react';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
|
||||
const LinkedLogo = () => (
|
||||
<a
|
||||
className="logo"
|
||||
href={`${getConfig().LMS_BASE_URL}/dashboard`}
|
||||
>
|
||||
<img
|
||||
className="d-block"
|
||||
src={getConfig().LOGO_URL}
|
||||
alt={getConfig().SITE_NAME}
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
|
||||
export default LinkedLogo;
|
||||
@@ -1,20 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import LinkedLogo from './LinkedLogo';
|
||||
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
getConfig: () => ({
|
||||
LMS_BASE_URL: '<getConfig().LMS_BASE_URL>',
|
||||
LOGO_URL: '<getConfig().LOGO_URL>',
|
||||
SITE_NAME: '<getConfig().SITE_NAME>',
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('Header CourseLabel component', () => {
|
||||
test('snapshot', () => {
|
||||
expect(
|
||||
shallow(<LinkedLogo />),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Header AnonymousUserMenu component snapshot 1`] = `
|
||||
<div>
|
||||
<Button
|
||||
className="mr-3"
|
||||
href="<LMS_BASE_URL>/register?next=http%3A%2F%2Flocalhost%2F"
|
||||
variant="outline-primary"
|
||||
>
|
||||
Register
|
||||
</Button>
|
||||
<Button
|
||||
href="redirect:http://localhost/"
|
||||
variant="primary"
|
||||
>
|
||||
Sign in
|
||||
</Button>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,25 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Header CourseLabel component snapshot 1`] = `
|
||||
<div
|
||||
className="flex-grow-1 course-title-lockup"
|
||||
style={
|
||||
Object {
|
||||
"lineHeight": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="d-block small m-0"
|
||||
>
|
||||
course-org
|
||||
|
||||
course-number
|
||||
</span>
|
||||
<span
|
||||
className="d-block m-0 font-weight-bold course-title"
|
||||
>
|
||||
course-title
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,14 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Header CourseLabel component snapshot 1`] = `
|
||||
<a
|
||||
className="logo"
|
||||
href="<getConfig().LMS_BASE_URL>/dashboard"
|
||||
>
|
||||
<img
|
||||
alt="<getConfig().SITE_NAME>"
|
||||
className="d-block"
|
||||
src="<getConfig().LOGO_URL>"
|
||||
/>
|
||||
</a>
|
||||
`;
|
||||
@@ -1,51 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Header component snapshot 1`] = `
|
||||
<header
|
||||
className="course-header"
|
||||
>
|
||||
<a
|
||||
className="sr-only sr-only-focusable"
|
||||
href="#main-content"
|
||||
>
|
||||
Skip to main content.
|
||||
</a>
|
||||
<div
|
||||
className="container-xl py-2 d-flex align-items-center"
|
||||
>
|
||||
<LinkedLogo />
|
||||
<CourseLabel
|
||||
courseNumber="course-number"
|
||||
courseOrg="course-org"
|
||||
courseTitle="course-title"
|
||||
/>
|
||||
<AnonymousUserMenu />
|
||||
</div>
|
||||
</header>
|
||||
`;
|
||||
|
||||
exports[`Header component snapshot with authenticatedUser 1`] = `
|
||||
<header
|
||||
className="course-header"
|
||||
>
|
||||
<a
|
||||
className="sr-only sr-only-focusable"
|
||||
href="#main-content"
|
||||
>
|
||||
Skip to main content.
|
||||
</a>
|
||||
<div
|
||||
className="container-xl py-2 d-flex align-items-center"
|
||||
>
|
||||
<LinkedLogo />
|
||||
<CourseLabel
|
||||
courseNumber="course-number"
|
||||
courseOrg="course-org"
|
||||
courseTitle="course-title"
|
||||
/>
|
||||
<AuthenticatedUserDropdown
|
||||
username="test"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
`;
|
||||
@@ -1,47 +0,0 @@
|
||||
import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
|
||||
import AnonymousUserMenu from './AnonymousUserMenu';
|
||||
import AuthenticatedUserDropdown from './AuthenticatedUserDropdown';
|
||||
import LinkedLogo from './LinkedLogo';
|
||||
import CourseLabel from './CourseLabel';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
export const Header = ({
|
||||
courseOrg,
|
||||
courseNumber,
|
||||
courseTitle,
|
||||
intl,
|
||||
}) => {
|
||||
const { authenticatedUser } = useContext(AppContext);
|
||||
return (
|
||||
<header className="course-header">
|
||||
<a className="sr-only sr-only-focusable" href="#main-content">
|
||||
{intl.formatMessage(messages.skipNavLink)}
|
||||
</a>
|
||||
<div className="container-xl py-2 d-flex align-items-center">
|
||||
<LinkedLogo />
|
||||
<CourseLabel {...{ courseOrg, courseNumber, courseTitle }} />
|
||||
{authenticatedUser
|
||||
? (<AuthenticatedUserDropdown username={authenticatedUser.username} />)
|
||||
: (<AnonymousUserMenu />)}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
Header.propTypes = {
|
||||
courseOrg: PropTypes.string,
|
||||
courseNumber: PropTypes.string,
|
||||
courseTitle: PropTypes.string,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
Header.defaultProps = {
|
||||
courseOrg: null,
|
||||
courseNumber: null,
|
||||
courseTitle: null,
|
||||
};
|
||||
|
||||
export default injectIntl(Header);
|
||||
@@ -1,38 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
import { Header } from '.';
|
||||
|
||||
jest.mock('./AnonymousUserMenu', () => 'AnonymousUserMenu');
|
||||
jest.mock('./AuthenticatedUserDropdown', () => 'AuthenticatedUserDropdown');
|
||||
jest.mock('./LinkedLogo', () => 'LinkedLogo');
|
||||
jest.mock('./CourseLabel', () => 'CourseLabel');
|
||||
|
||||
jest.mock('@edx/frontend-platform/react', () => ({
|
||||
AppContext: { authenticatedUser: null },
|
||||
}));
|
||||
jest.mock('react', () => ({
|
||||
...jest.requireActual('react'),
|
||||
useContext: (context) => context,
|
||||
}));
|
||||
|
||||
const courseData = {
|
||||
courseOrg: 'course-org',
|
||||
courseNumber: 'course-number',
|
||||
courseTitle: 'course-title',
|
||||
};
|
||||
|
||||
describe('Header component', () => {
|
||||
const props = {
|
||||
...courseData,
|
||||
intl: { formatMessage: (msg) => msg.defaultMessage },
|
||||
};
|
||||
test('snapshot', () => {
|
||||
expect(shallow(<Header {...props} />)).toMatchSnapshot();
|
||||
});
|
||||
test('snapshot with authenticatedUser', () => {
|
||||
AppContext.authenticatedUser = { username: 'test' };
|
||||
expect(shallow(<Header {...props} />)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
courseMaterial: {
|
||||
id: 'learn.navigation.course.tabs.label',
|
||||
defaultMessage: 'Course Material',
|
||||
description: 'The accessible label for course tabs navigation',
|
||||
},
|
||||
dashboard: {
|
||||
id: 'header.menu.dashboard.label',
|
||||
defaultMessage: 'Dashboard',
|
||||
description: 'The text for the user menu Dashboard navigation link.',
|
||||
},
|
||||
help: {
|
||||
id: 'header.help.label',
|
||||
defaultMessage: 'Help',
|
||||
description: 'The text for the link to the Help Center',
|
||||
},
|
||||
profile: {
|
||||
id: 'header.menu.profile.label',
|
||||
defaultMessage: 'Profile',
|
||||
description: 'The text for the user menu Profile navigation link.',
|
||||
},
|
||||
account: {
|
||||
id: 'header.menu.account.label',
|
||||
defaultMessage: 'Account',
|
||||
description: 'The text for the user menu Account navigation link.',
|
||||
},
|
||||
orderHistory: {
|
||||
id: 'header.menu.orderHistory.label',
|
||||
defaultMessage: 'Order History',
|
||||
description: 'The text for the user menu Order History navigation link.',
|
||||
},
|
||||
skipNavLink: {
|
||||
id: 'header.navigation.skipNavLink',
|
||||
defaultMessage: 'Skip to main content.',
|
||||
description: 'A link used by screen readers to allow users to skip to the main content of the page.',
|
||||
},
|
||||
signOut: {
|
||||
id: 'header.menu.signOut.label',
|
||||
defaultMessage: 'Sign Out',
|
||||
description: 'The label for the user menu Sign Out action.',
|
||||
},
|
||||
registerSentenceCase: {
|
||||
id: 'header.register.sentenceCase',
|
||||
defaultMessage: 'Register',
|
||||
description: 'Text in a button, prompting the user to register.',
|
||||
},
|
||||
signInSentenceCase: {
|
||||
id: 'header.signIn.sentenceCase',
|
||||
defaultMessage: 'Sign in',
|
||||
description: 'Text in a button, prompting the user to log in.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -16,7 +16,7 @@ exports[`DemoWarning component snapshots snapshot: disabled flag is present 1`]
|
||||
</Alert.Heading>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="You are using the Demo Mode of the new Enhanced ORA Staff Grader interface. You will be unable to submit grades until you activate the feature."
|
||||
defaultMessage="You are demoing the new ORA staff grading experience. You will be unable to submit grades until you activate the feature. This will become the default grading experience on May 9th (05/09/2022). To opt-in early, or opt-out, please contact Partner Support."
|
||||
description="Demo mode message"
|
||||
id="ora-grading.ReviewModal.demoMessage"
|
||||
/>
|
||||
|
||||
@@ -10,7 +10,7 @@ const messages = defineMessages({
|
||||
},
|
||||
demoModeMessage: {
|
||||
id: 'ora-grading.ReviewModal.demoMessage',
|
||||
defaultMessage: 'You are using the Demo Mode of the new Enhanced ORA Staff Grader interface. You will be unable to submit grades until you activate the feature.',
|
||||
defaultMessage: 'You are demoing the new ORA staff grading experience. You will be unable to submit grades until you activate the feature. This will become the default grading experience on May 9th (05/09/2022). To opt-in early, or opt-out, please contact Partner Support.',
|
||||
description: 'Demo mode message',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,11 +2,13 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
Card, Collapsible, Icon, DataTable,
|
||||
Card, Collapsible, Icon, DataTable, Button,
|
||||
} from '@edx/paragon';
|
||||
import { ArrowDropDown, ArrowDropUp } from '@edx/paragon/icons';
|
||||
import { ArrowDropDown, ArrowDropUp, WarningFilled } from '@edx/paragon/icons';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { downloadAllLimit, downloadSingleLimit } from 'data/constants/files';
|
||||
|
||||
import FileNameCell from './components/FileNameCell';
|
||||
import FileExtensionCell from './components/FileExtensionCell';
|
||||
import FilePopoverCell from './components/FilePopoverCell';
|
||||
@@ -19,7 +21,17 @@ import messages from './messages';
|
||||
*/
|
||||
export class SubmissionFiles extends React.Component {
|
||||
get title() {
|
||||
return `Submission Files (${this.props.files.length})`;
|
||||
return `${this.props.intl.formatMessage(messages.submissionFiles)} (${this.props.files.length})`;
|
||||
}
|
||||
|
||||
get canDownload() {
|
||||
let totalFileSize = 0;
|
||||
const exceedFileSize = this.props.files.some(file => {
|
||||
totalFileSize += file.size;
|
||||
return file.size > downloadSingleLimit;
|
||||
});
|
||||
|
||||
return !exceedFileSize && totalFileSize < downloadAllLimit;
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -70,7 +82,15 @@ export class SubmissionFiles extends React.Component {
|
||||
</Collapsible.Body>
|
||||
</Collapsible.Advanced>
|
||||
<Card.Footer className="text-right">
|
||||
<FileDownload files={files} />
|
||||
{
|
||||
this.canDownload ? <FileDownload files={files} /> : (
|
||||
<div>
|
||||
<Icon className="d-inline-block align-middle" src={WarningFilled} />
|
||||
<span className="exceed-download-text"> {intl.formatMessage(messages.exceedFileSize)} </span>
|
||||
<Button disabled>{intl.formatMessage(messages.downloadFiles)}</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Card.Footer>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { downloadAllLimit, downloadSingleLimit } from 'data/constants/files';
|
||||
|
||||
import { formatMessage } from 'testUtils';
|
||||
import { SubmissionFiles } from './SubmissionFiles';
|
||||
import messages from './messages';
|
||||
|
||||
jest.mock('./components/FileNameCell', () => jest.fn().mockName('FileNameCell'));
|
||||
jest.mock('./components/FileExtensionCell', () => jest.fn().mockName('FileExtensionCell'));
|
||||
@@ -16,25 +19,34 @@ describe('SubmissionFiles', () => {
|
||||
name: 'some file name.jpg',
|
||||
description: 'description for the file',
|
||||
downloadURL: '/valid-url-wink-wink',
|
||||
size: 0,
|
||||
},
|
||||
{
|
||||
name: 'file number 2.jpg',
|
||||
description: 'description for this file',
|
||||
downloadURL: '/url-2',
|
||||
size: 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
let el;
|
||||
beforeAll(() => {
|
||||
el = shallow(<SubmissionFiles intl={{ formatMessage }} />);
|
||||
beforeEach(() => {
|
||||
el = shallow(<SubmissionFiles intl={{ formatMessage }} {...props} />);
|
||||
});
|
||||
|
||||
describe('snapshot', () => {
|
||||
test('files does not exist', () => {
|
||||
test('files existed for props', () => {
|
||||
expect(el).toMatchSnapshot();
|
||||
});
|
||||
test('files exited for props', () => {
|
||||
el.setProps({ ...props });
|
||||
|
||||
test('files does not exist', () => {
|
||||
el.setProps({ files: [] });
|
||||
|
||||
expect(el).toMatchSnapshot();
|
||||
});
|
||||
test('files size exceed', () => {
|
||||
const files = props.files.map(file => ({ ...file, size: downloadSingleLimit + 1 }));
|
||||
el.setProps({ files });
|
||||
expect(el).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -43,12 +55,47 @@ describe('SubmissionFiles', () => {
|
||||
test('title', () => {
|
||||
const titleEl = el.find('.submission-files-title>h3');
|
||||
expect(titleEl.text()).toEqual(
|
||||
`Submission Files (${props.files.length})`,
|
||||
`${formatMessage(messages.submissionFiles)} (${props.files.length})`,
|
||||
);
|
||||
expect(el.instance().title).toEqual(
|
||||
`Submission Files (${props.files.length})`,
|
||||
`${formatMessage(messages.submissionFiles)} (${props.files.length})`,
|
||||
);
|
||||
});
|
||||
|
||||
describe('canDownload', () => {
|
||||
test('normal file size', () => {
|
||||
expect(el.instance().canDownload).toEqual(true);
|
||||
});
|
||||
|
||||
test('one of the file exceed the limit', () => {
|
||||
const oneFileExceed = [{ ...props.files[0], size: downloadSingleLimit + 1 }, props.files[1]];
|
||||
|
||||
oneFileExceed.forEach(file => expect(file.size < downloadAllLimit).toEqual(true));
|
||||
|
||||
el.setProps({ files: oneFileExceed });
|
||||
expect(el.instance().canDownload).toEqual(false);
|
||||
|
||||
const warningEl = el.find('span.exceed-download-text');
|
||||
expect(warningEl.text().trim()).toEqual(formatMessage(messages.exceedFileSize));
|
||||
});
|
||||
|
||||
test('total file size exceed the limit', () => {
|
||||
const length = 20;
|
||||
const totalFilesExceed = new Array(length).fill({
|
||||
name: 'some file name.jpg',
|
||||
description: 'description for the file',
|
||||
downloadURL: '/valid-url-wink-wink',
|
||||
size: (downloadAllLimit + 1) / length,
|
||||
});
|
||||
totalFilesExceed.forEach(file => {
|
||||
expect(file.size < downloadAllLimit).toEqual(true);
|
||||
expect(file.size < downloadSingleLimit).toEqual(true);
|
||||
});
|
||||
|
||||
el.setProps({ files: totalFilesExceed });
|
||||
expect(el.instance().canDownload).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ exports[`SubmissionFiles component snapshot files does not exist 1`] = `
|
||||
</Card>
|
||||
`;
|
||||
|
||||
exports[`SubmissionFiles component snapshot files exited for props 1`] = `
|
||||
exports[`SubmissionFiles component snapshot files existed for props 1`] = `
|
||||
<Card
|
||||
className="submission-files"
|
||||
>
|
||||
@@ -75,11 +75,13 @@ exports[`SubmissionFiles component snapshot files exited for props 1`] = `
|
||||
"description": "description for the file",
|
||||
"downloadURL": "/valid-url-wink-wink",
|
||||
"name": "some file name.jpg",
|
||||
"size": 0,
|
||||
},
|
||||
Object {
|
||||
"description": "description for this file",
|
||||
"downloadURL": "/url-2",
|
||||
"name": "file number 2.jpg",
|
||||
"size": 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -100,11 +102,13 @@ exports[`SubmissionFiles component snapshot files exited for props 1`] = `
|
||||
"description": "description for the file",
|
||||
"downloadURL": "/valid-url-wink-wink",
|
||||
"name": "some file name.jpg",
|
||||
"size": 0,
|
||||
},
|
||||
Object {
|
||||
"description": "description for this file",
|
||||
"downloadURL": "/url-2",
|
||||
"name": "file number 2.jpg",
|
||||
"size": 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -112,3 +116,105 @@ exports[`SubmissionFiles component snapshot files exited for props 1`] = `
|
||||
</Card.Footer>
|
||||
</Card>
|
||||
`;
|
||||
|
||||
exports[`SubmissionFiles component snapshot files size exceed 1`] = `
|
||||
<Card
|
||||
className="submission-files"
|
||||
>
|
||||
<Collapsible.Advanced
|
||||
defaultOpen={true}
|
||||
>
|
||||
<Collapsible.Trigger
|
||||
className="submission-files-title"
|
||||
>
|
||||
<h3>
|
||||
Submission Files (2)
|
||||
</h3>
|
||||
<Collapsible.Visible
|
||||
whenClosed={true}
|
||||
>
|
||||
<Icon
|
||||
src={[MockFunction icons.ArrowDropDown]}
|
||||
/>
|
||||
</Collapsible.Visible>
|
||||
<Collapsible.Visible
|
||||
whenOpen={true}
|
||||
>
|
||||
<Icon
|
||||
src={[MockFunction icons.ArrowDropUp]}
|
||||
/>
|
||||
</Collapsible.Visible>
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Body
|
||||
className="submission-files-body"
|
||||
>
|
||||
<div
|
||||
className="submission-files-table"
|
||||
>
|
||||
<DataTable
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"Cell": [MockFunction FileNameCell],
|
||||
"Header": "Name",
|
||||
"accessor": "name",
|
||||
},
|
||||
Object {
|
||||
"Cell": [MockFunction FileExtensionCell],
|
||||
"Header": "File Extension",
|
||||
"accessor": "name",
|
||||
"id": "extension",
|
||||
},
|
||||
Object {
|
||||
"Cell": [MockFunction FilePopoverCell],
|
||||
"Header": "File Metadata",
|
||||
"accessor": "",
|
||||
},
|
||||
]
|
||||
}
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"description": "description for the file",
|
||||
"downloadURL": "/valid-url-wink-wink",
|
||||
"name": "some file name.jpg",
|
||||
"size": 1610612737,
|
||||
},
|
||||
Object {
|
||||
"description": "description for this file",
|
||||
"downloadURL": "/url-2",
|
||||
"name": "file number 2.jpg",
|
||||
"size": 1610612737,
|
||||
},
|
||||
]
|
||||
}
|
||||
itemCount={2}
|
||||
>
|
||||
<DataTable.Table />
|
||||
</DataTable>
|
||||
</div>
|
||||
</Collapsible.Body>
|
||||
</Collapsible.Advanced>
|
||||
<Card.Footer
|
||||
className="text-right"
|
||||
>
|
||||
<div>
|
||||
<Icon
|
||||
className="d-inline-block align-middle"
|
||||
/>
|
||||
<span
|
||||
className="exceed-download-text"
|
||||
>
|
||||
|
||||
Exceeded the allow download size
|
||||
|
||||
</span>
|
||||
<Button
|
||||
disabled={true}
|
||||
>
|
||||
Download files
|
||||
</Button>
|
||||
</div>
|
||||
</Card.Footer>
|
||||
</Card>
|
||||
`;
|
||||
|
||||
@@ -36,6 +36,16 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Retry download',
|
||||
description: 'Download files failed state label',
|
||||
},
|
||||
submissionFiles: {
|
||||
id: 'ora-grading.ResponseDisplay.SubmissionFiles.submissionFile',
|
||||
defaultMessage: 'Submission Files',
|
||||
description: 'Total submission files',
|
||||
},
|
||||
exceedFileSize: {
|
||||
id: 'ora-grading.ResponseDisplay.SubmissionFiles.fileSizeExceed',
|
||||
defaultMessage: 'Exceeded the allow download size',
|
||||
description: 'Exceed the allow download size error message',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -28,7 +28,7 @@ export class DownloadErrors extends React.Component {
|
||||
if (!this.props.isFailed) { return null; }
|
||||
return (
|
||||
<ReviewError
|
||||
key="lockFailed"
|
||||
key="downloadFailed"
|
||||
headingMessage={messages.downloadFailedHeading}
|
||||
actions={{
|
||||
cancel: { onClick: this.cancelAction, message: messages.dismiss },
|
||||
@@ -36,19 +36,36 @@ export class DownloadErrors extends React.Component {
|
||||
}}
|
||||
>
|
||||
<FormattedMessage {...messages.downloadFailedContent} />
|
||||
<br />
|
||||
<FormattedMessage {...messages.failedFiles} />
|
||||
<ul>
|
||||
{this.props.error.files.map(filename => (
|
||||
<li key={filename}>{filename}</li>
|
||||
))}
|
||||
</ul>
|
||||
</ReviewError>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DownloadErrors.defaultProps = {
|
||||
error: {
|
||||
files: [],
|
||||
},
|
||||
};
|
||||
DownloadErrors.propTypes = {
|
||||
// redux
|
||||
clearState: PropTypes.func.isRequired,
|
||||
isFailed: PropTypes.bool.isRequired,
|
||||
error: PropTypes.shape({
|
||||
files: PropTypes.arrayOf(PropTypes.string),
|
||||
}),
|
||||
downloadFiles: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export const mapStateToProps = (state) => ({
|
||||
isFailed: selectors.requests.isFailed(state, { requestKey: RequestKeys.downloadFiles }),
|
||||
error: selectors.requests.error(state, { requestKey: RequestKeys.downloadFiles }),
|
||||
});
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
|
||||
@@ -14,7 +14,10 @@ let el;
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
selectors: {
|
||||
requests: { isFailed: (...args) => ({ isFailed: args }) },
|
||||
requests: {
|
||||
isFailed: (...args) => ({ isFailed: args }),
|
||||
error: (...args) => ({ error: args }),
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
requests: { clearRequest: jest.fn() },
|
||||
@@ -28,6 +31,9 @@ jest.mock('./ReviewError', () => 'ReviewError');
|
||||
describe('DownloadErrors component', () => {
|
||||
const props = {
|
||||
isFailed: false,
|
||||
error: {
|
||||
files: [],
|
||||
},
|
||||
};
|
||||
describe('component', () => {
|
||||
beforeEach(() => {
|
||||
@@ -40,7 +46,12 @@ describe('DownloadErrors component', () => {
|
||||
el.instance().cancelAction = jest.fn().mockName('this.cancelAction');
|
||||
});
|
||||
test('failed: show error', () => {
|
||||
el.setProps({ isFailed: true });
|
||||
el.setProps({
|
||||
isFailed: true,
|
||||
error: {
|
||||
files: ['file-1-failed.error', 'file-2.failed'],
|
||||
},
|
||||
});
|
||||
expect(el.instance().render()).toMatchSnapshot();
|
||||
expect(el.isEmptyRender()).toEqual(false);
|
||||
});
|
||||
@@ -68,6 +79,10 @@ describe('DownloadErrors component', () => {
|
||||
const requestKey = RequestKeys.downloadFiles;
|
||||
expect(mapped.isFailed).toEqual(selectors.requests.isFailed(testState, { requestKey }));
|
||||
});
|
||||
test('error loads from requests.error(downloadFiles)', () => {
|
||||
const requestKey = RequestKeys.downloadFiles;
|
||||
expect(mapped.error).toEqual(selectors.requests.error(testState, { requestKey }));
|
||||
});
|
||||
});
|
||||
describe('mapDispatchToProps', () => {
|
||||
it('loads clearState from actions.requests.clearRequest', () => {
|
||||
|
||||
@@ -34,6 +34,20 @@ exports[`DownloadErrors component component snapshots failed: show error 1`] = `
|
||||
description="Failed download error content"
|
||||
id="ora-grading.ReviewModal.errorDownloadFailedContent"
|
||||
/>
|
||||
<br />
|
||||
<FormattedMessage
|
||||
defaultMessage="Failed files:"
|
||||
description="List header for file download failure alert"
|
||||
id="ora-grading.ReviewModal.errorDownloadFailedFiles"
|
||||
/>
|
||||
<ul>
|
||||
<li>
|
||||
file-1-failed.error
|
||||
</li>
|
||||
<li>
|
||||
file-2.failed
|
||||
</li>
|
||||
</ul>
|
||||
</ReviewError>
|
||||
`;
|
||||
|
||||
|
||||
@@ -82,6 +82,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Retry download',
|
||||
description: 'Failed download retry button text',
|
||||
},
|
||||
failedFiles: {
|
||||
id: 'ora-grading.ReviewModal.errorDownloadFailedFiles',
|
||||
defaultMessage: 'Failed files:',
|
||||
description: 'List header for file download failure alert',
|
||||
},
|
||||
});
|
||||
|
||||
export default StrictDict(messages);
|
||||
|
||||
@@ -14,4 +14,7 @@ export const FileTypes = StrictDict({
|
||||
svg: 'svg',
|
||||
});
|
||||
|
||||
export const downloadSingleLimit = 1610612736; // 1.5GB
|
||||
export const downloadAllLimit = 10737418240; // 10GB
|
||||
|
||||
export default FileTypes;
|
||||
|
||||
@@ -124,44 +124,46 @@ rubric.criteriaIndices = createSelector(
|
||||
* Returns true iff the passed feedback value is required or optional
|
||||
* @return {bool} - should include feedback?
|
||||
*/
|
||||
const shouldIncludeFeedback = (feedback) => ([
|
||||
export const shouldIncludeFeedback = (feedback) => [
|
||||
feedbackRequirement.required,
|
||||
feedbackRequirement.optional,
|
||||
]).includes(feedback);
|
||||
].includes(feedback);
|
||||
|
||||
/**
|
||||
* Returns an empty grade data object based on the rubric config loaded in the app model.
|
||||
* @return {obj} - empty grade data object
|
||||
* take current grade and fill the empty fill with default value
|
||||
* @param {obj} gradeData
|
||||
* @returns
|
||||
*/
|
||||
export const emptyGrade = createSelector(
|
||||
[module.rubric.hasConfig, module.rubric.criteria, module.rubric.feedbackConfig],
|
||||
(hasConfig, criteria, feedbackConfig) => {
|
||||
if (!hasConfig) {
|
||||
return null;
|
||||
}
|
||||
const gradeData = {};
|
||||
if (shouldIncludeFeedback(feedbackConfig)) {
|
||||
gradeData.overallFeedback = '';
|
||||
}
|
||||
gradeData.criteria = criteria.map(criterion => {
|
||||
const entry = {
|
||||
orderNum: criterion.orderNum,
|
||||
name: criterion.name,
|
||||
selectedOption: '',
|
||||
};
|
||||
if (shouldIncludeFeedback(criterion.feedback)) {
|
||||
entry.feedback = '';
|
||||
}
|
||||
return entry;
|
||||
});
|
||||
return gradeData;
|
||||
},
|
||||
);
|
||||
export const fillGradeData = (state, data) => {
|
||||
const hasConfig = module.rubric.hasConfig(state);
|
||||
if (!hasConfig || Array.isArray(data?.criteria)) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const feedbackConfig = module.rubric.feedbackConfig(state);
|
||||
const criteria = module.rubric.criteria(state);
|
||||
|
||||
const overallFeedback = (
|
||||
module.shouldIncludeFeedback(feedbackConfig) && { overallFeedback: '' }
|
||||
);
|
||||
const criteriaFeedback = (feedback) => (
|
||||
module.shouldIncludeFeedback(feedback) && { feedback: '' }
|
||||
);
|
||||
|
||||
const gradeData = { ...overallFeedback };
|
||||
gradeData.criteria = criteria.map(({ feedback, name, orderNum }) => ({
|
||||
...criteriaFeedback(feedback),
|
||||
name,
|
||||
orderNum,
|
||||
selectedOption: '',
|
||||
}));
|
||||
return gradeData;
|
||||
};
|
||||
|
||||
export default StrictDict({
|
||||
...simpleSelectors,
|
||||
courseId,
|
||||
ora,
|
||||
rubric: StrictDict(rubric),
|
||||
emptyGrade,
|
||||
fillGradeData,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { feedbackRequirement } from 'data/services/lms/constants';
|
||||
|
||||
import { keyStore } from '../../../utils';
|
||||
// import * in order to mock in-file references
|
||||
import * as selectors from './selectors';
|
||||
|
||||
@@ -44,6 +45,8 @@ const testState = {
|
||||
},
|
||||
};
|
||||
|
||||
const selectorKeys = keyStore(selectors);
|
||||
|
||||
describe('app selectors unit tests', () => {
|
||||
const { appSelector, simpleSelectors, rubric } = selectors;
|
||||
describe('appSelector', () => {
|
||||
@@ -180,56 +183,86 @@ describe('app selectors unit tests', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('emptyGrade selector', () => {
|
||||
const { rubricConfig } = testState.app.oraMetadata;
|
||||
let preSelectors;
|
||||
let cb;
|
||||
describe('shouldIncludeFeedback', () => {
|
||||
it('returns true iff the passed feedback is optional or required', () => {
|
||||
expect(selectors.shouldIncludeFeedback(feedbackRequirement.optional)).toEqual(true);
|
||||
expect(selectors.shouldIncludeFeedback(feedbackRequirement.required)).toEqual(true);
|
||||
expect(selectors.shouldIncludeFeedback(feedbackRequirement.disabled)).toEqual(false);
|
||||
expect(selectors.shouldIncludeFeedback('aribitrary')).toEqual(false);
|
||||
});
|
||||
});
|
||||
describe('fillGradeData selector', () => {
|
||||
const cb = selectors.fillGradeData;
|
||||
const spies = {};
|
||||
let oldRubric;
|
||||
const criteria = [
|
||||
{ name: 'criteria1', orderNum: 0, feedback: true },
|
||||
{ name: 'criteria2', orderNum: 1, feedback: false },
|
||||
{ name: 'criteria3', orderNum: 2, feedback: true },
|
||||
];
|
||||
|
||||
const data = { arbitrary: 'data', criteria };
|
||||
beforeAll(() => {
|
||||
oldRubric = { ...rubric };
|
||||
});
|
||||
beforeEach(() => {
|
||||
({ preSelectors, cb } = selectors.emptyGrade);
|
||||
rubric.hasConfig = jest.fn(() => true);
|
||||
rubric.feedbackConfig = jest.fn(() => true);
|
||||
rubric.criteria = jest.fn(() => criteria);
|
||||
spies.shouldIncludeFeedback = jest.spyOn(
|
||||
selectors,
|
||||
selectorKeys.shouldIncludeFeedback,
|
||||
).mockImplementation(val => val);
|
||||
});
|
||||
it('is a memoized selector based on rubric.[hasConfig, criteria, feedbackConfig]', () => {
|
||||
expect(preSelectors).toEqual([
|
||||
rubric.hasConfig,
|
||||
rubric.criteria,
|
||||
rubric.feedbackConfig,
|
||||
]);
|
||||
afterEach(() => {
|
||||
spies[selectorKeys.shouldIncludeFeedback].mockRestore();
|
||||
});
|
||||
describe('If the config is not loaded (hasConfig = undefined)', () => {
|
||||
it('returns null', () => {
|
||||
expect(cb(false, {}, '')).toEqual(null);
|
||||
afterAll(() => {
|
||||
selectors.rubric = { ...oldRubric };
|
||||
});
|
||||
|
||||
describe('if rubric config is not loaded', () => {
|
||||
it('returns passed gradeData', () => {
|
||||
rubric.hasConfig.mockReturnValueOnce(false);
|
||||
expect(cb(testState, data)).toEqual(data);
|
||||
});
|
||||
});
|
||||
describe('The generated object', () => {
|
||||
it('loads an overallFeedback field iff feedbackConfig is optional or required', () => {
|
||||
let gradeData = cb(true, rubricConfig.criteria, feedbackRequirement.optional);
|
||||
expect(gradeData.overallFeedback).toEqual('');
|
||||
gradeData = cb(true, rubricConfig.criteria, feedbackRequirement.required);
|
||||
expect(gradeData.overallFeedback).toEqual('');
|
||||
gradeData = cb(true, rubricConfig.criteria, feedbackRequirement.disabled);
|
||||
expect(gradeData.overallFeedback).toEqual(undefined);
|
||||
|
||||
describe('if rubric config is loaded', () => {
|
||||
describe('gradeData is passed, contains criteria', () => {
|
||||
it('returns the passed gradeData', () => {
|
||||
expect(cb(testState, data)).toEqual(data);
|
||||
});
|
||||
});
|
||||
it('loads criteria with feedback field based on requirement config', () => {
|
||||
const gradeData = cb(true, rubricConfig.criteria, rubricConfig.feedback);
|
||||
const { criteria } = rubricConfig;
|
||||
expect(gradeData.criteria).toEqual([
|
||||
{
|
||||
orderNum: criteria[0].orderNum,
|
||||
name: criteria[0].name,
|
||||
selectedOption: '',
|
||||
feedback: '',
|
||||
},
|
||||
{
|
||||
orderNum: criteria[1].orderNum,
|
||||
name: criteria[1].name,
|
||||
selectedOption: '',
|
||||
},
|
||||
{
|
||||
orderNum: criteria[2].orderNum,
|
||||
name: criteria[2].name,
|
||||
selectedOption: '',
|
||||
feedback: '',
|
||||
},
|
||||
]);
|
||||
describe('gradeData is not passed', () => {
|
||||
it('adds overall feedback iff is configured for inclusion', () => {
|
||||
expect(cb(testState, null).overallFeedback).toEqual('');
|
||||
rubric.feedbackConfig.mockReturnValueOnce(false);
|
||||
expect(cb(testState, null).overallFeedback).toEqual(undefined);
|
||||
});
|
||||
describe('criteria', () => {
|
||||
it('displays name, orderNum, and feedback per config and empty selection', () => {
|
||||
expect(cb(testState, null).criteria).toEqual([
|
||||
{
|
||||
name: criteria[0].name,
|
||||
orderNum: criteria[0].orderNum,
|
||||
feedback: '',
|
||||
selectedOption: '',
|
||||
},
|
||||
{
|
||||
name: criteria[1].name,
|
||||
orderNum: criteria[1].orderNum,
|
||||
selectedOption: '',
|
||||
},
|
||||
{
|
||||
name: criteria[2].name,
|
||||
orderNum: criteria[2].orderNum,
|
||||
feedback: '',
|
||||
selectedOption: '',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import * as zip from '@zip.js/zip.js';
|
||||
import FileSaver from 'file-saver';
|
||||
|
||||
import { StrictDict } from 'utils';
|
||||
import { RequestKeys } from 'data/constants/requests';
|
||||
import { selectors } from 'data/redux';
|
||||
import { locationId } from 'data/constants/app';
|
||||
import { stringifyUrl } from 'data/services/lms/utils';
|
||||
|
||||
import { networkRequest } from './requests';
|
||||
import * as module from './download';
|
||||
|
||||
export const ERRORS = StrictDict({
|
||||
fetchFailed: 'Fetch failed',
|
||||
export const DownloadException = (files) => ({
|
||||
files,
|
||||
name: 'DownloadException',
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -21,22 +23,13 @@ export const genManifest = (files) => files.map(
|
||||
(file) => `Filename: ${file.name}\nDescription: ${file.description}\nSize: ${file.size}`,
|
||||
).join('\n\n');
|
||||
|
||||
/**
|
||||
* Returns the zip filename
|
||||
* @return {string} - zip download file name
|
||||
*/
|
||||
export const zipFileName = () => {
|
||||
const currentDate = new Date().getTime();
|
||||
return `ora-files-download-${currentDate}.zip`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Zip the blob output of a set of files with a manifest file.
|
||||
* @param {obj[]} files - list of file entries with downloadUrl, name, and description
|
||||
* @param {blob[]} blobs - file content blobs
|
||||
* @return {Promise} - zip async process promise.
|
||||
*/
|
||||
export const zipFiles = async (files, blobs) => {
|
||||
export const zipFiles = async (files, blobs, username) => {
|
||||
const zipWriter = new zip.ZipWriter(new zip.BlobWriter('application/zip'));
|
||||
await zipWriter.add('manifest.txt', new zip.TextReader(module.genManifest(files)));
|
||||
|
||||
@@ -50,24 +43,60 @@ export const zipFiles = async (files, blobs) => {
|
||||
}
|
||||
|
||||
const zipFile = await zipWriter.close();
|
||||
FileSaver.saveAs(zipFile, module.zipFileName());
|
||||
const zipName = `${username}-${locationId}.zip`;
|
||||
FileSaver.saveAs(zipFile, zipName);
|
||||
};
|
||||
|
||||
/**
|
||||
* generate url with additional timestamp for cache busting.
|
||||
* This is implemented for fixing issue with the browser not
|
||||
* allowing the user to fetch the same url as the image tag.
|
||||
* @param {string} url
|
||||
* @returns {string}
|
||||
*/
|
||||
export const getTimeStampUrl = (url) => stringifyUrl(url, {
|
||||
ora_grading_download_timestamp: new Date().getTime(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Download a file and return its blob is successful, or null if not.
|
||||
* @param {obj} file - file entry with downloadUrl
|
||||
* @return {blob} - file blob or null
|
||||
* @return {Promise} - file blob or null
|
||||
*/
|
||||
export const downloadFile = (file) => fetch(file.downloadUrl).then(resp => (
|
||||
resp.ok ? resp.blob() : null
|
||||
));
|
||||
export const downloadFile = (file) => fetch(
|
||||
module.getTimeStampUrl(file.downloadUrl),
|
||||
).then((response) => {
|
||||
if (!response.ok) {
|
||||
// This is necessary because some of the error such as 404 does not throw.
|
||||
// Due to that inconsistency, I have decide to share catch statement like this.
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
return response.blob();
|
||||
});
|
||||
|
||||
/**
|
||||
* Download blobs given file objects. Returns a promise map.
|
||||
* @param {obj[]} files - list of file entries with downloadUrl, name, and description
|
||||
* @return {Promise[]} - Promise map of download attempts (null for failed fetches)
|
||||
*/
|
||||
export const downloadBlobs = (files) => Promise.all(files.map(module.downloadFile));
|
||||
export const downloadBlobs = async (files) => {
|
||||
const blobs = [];
|
||||
const errors = [];
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const file of files) {
|
||||
try {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
blobs.push(await module.downloadFile(file));
|
||||
} catch (error) {
|
||||
errors.push(file.name);
|
||||
}
|
||||
}
|
||||
if (errors.length) {
|
||||
throw DownloadException(errors);
|
||||
}
|
||||
return blobs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Download all files for the selected submission as a zip file.
|
||||
@@ -75,14 +104,10 @@ export const downloadBlobs = (files) => Promise.all(files.map(module.downloadFil
|
||||
*/
|
||||
export const downloadFiles = () => (dispatch, getState) => {
|
||||
const { files } = selectors.grading.selected.response(getState());
|
||||
const username = selectors.grading.selected.username(getState());
|
||||
dispatch(networkRequest({
|
||||
requestKey: RequestKeys.downloadFiles,
|
||||
promise: module.downloadBlobs(files).then(blobs => {
|
||||
if (blobs.some(blob => blob === null)) {
|
||||
throw Error(ERRORS.fetchFailed);
|
||||
}
|
||||
return module.zipFiles(files, blobs);
|
||||
}),
|
||||
promise: module.downloadBlobs(files).then(blobs => module.zipFiles(files, blobs, username)),
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
@@ -37,7 +37,10 @@ jest.mock('./requests', () => ({
|
||||
jest.mock('data/redux', () => ({
|
||||
selectors: {
|
||||
grading: {
|
||||
selected: { response: jest.fn() },
|
||||
selected: {
|
||||
response: jest.fn(),
|
||||
username: jest.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
@@ -55,6 +58,7 @@ describe('download thunkActions', () => {
|
||||
const files = [mockFile('test-file1.jpg'), mockFile('test-file2.pdf')];
|
||||
const blobs = ['blob1', 'blob2'];
|
||||
const response = { files };
|
||||
const username = 'student-name';
|
||||
let dispatch;
|
||||
const getState = () => testState;
|
||||
describe('genManifest', () => {
|
||||
@@ -67,13 +71,10 @@ describe('download thunkActions', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('zipFileName', () => {
|
||||
// add tests when name is more nailed down
|
||||
});
|
||||
describe('zipFiles', () => {
|
||||
test('zips files and manifest', () => {
|
||||
const mockZipWriter = new zip.ZipWriter();
|
||||
return download.zipFiles(files, blobs).then(() => {
|
||||
return download.zipFiles(files, blobs, username).then(() => {
|
||||
expect(mockZipWriter.files).toEqual([
|
||||
['manifest.txt', mockTextReader],
|
||||
[files[0].name, mockBlobReader],
|
||||
@@ -86,63 +87,98 @@ describe('download thunkActions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTimeStampUrl', () => {
|
||||
it('generate different url every milisecond for cache busting', () => {
|
||||
const testUrl = 'test/url?param1=true';
|
||||
const firstGen = download.getTimeStampUrl(testUrl);
|
||||
// fast forward for 1 milisecond
|
||||
jest.advanceTimersByTime(1);
|
||||
const secondGen = download.getTimeStampUrl(testUrl);
|
||||
expect(firstGen).not.toEqual(secondGen);
|
||||
});
|
||||
});
|
||||
|
||||
describe('downloadFile', () => {
|
||||
let fetch;
|
||||
let getTimeStampUrl;
|
||||
const blob = 'test-blob';
|
||||
const file = files[0];
|
||||
beforeEach(() => {
|
||||
fetch = window.fetch;
|
||||
window.fetch = jest.fn();
|
||||
getTimeStampUrl = download.getTimeStampUrl;
|
||||
download.getTimeStampUrl = jest.fn();
|
||||
});
|
||||
afterEach(() => {
|
||||
window.fetch = fetch;
|
||||
download.getTimeStampUrl = getTimeStampUrl;
|
||||
});
|
||||
it('returns blob output if successful', () => {
|
||||
window.fetch.mockReturnValue(Promise.resolve({ ok: true, blob: () => blob }));
|
||||
return download
|
||||
.downloadFile(files[0])
|
||||
.then((val) => expect(val).toEqual(blob));
|
||||
expect(download.downloadFile(file)).resolves.toEqual(blob);
|
||||
expect(download.getTimeStampUrl).toBeCalledWith(file.downloadUrl);
|
||||
});
|
||||
it('returns null if not successful', () => {
|
||||
window.fetch.mockReturnValue(Promise.resolve({ ok: false }));
|
||||
return download
|
||||
.downloadFile(files[0])
|
||||
.then((val) => expect(val).toEqual(null));
|
||||
it('throw if not successful', () => {
|
||||
const failFetchStatusText = 'failed to fetch';
|
||||
window.fetch.mockReturnValue(Promise.resolve({ ok: false, statusText: failFetchStatusText }));
|
||||
expect(() => download.downloadFile(file)).rejects.toThrow(failFetchStatusText);
|
||||
expect(download.getTimeStampUrl).toBeCalledWith(file.downloadUrl);
|
||||
});
|
||||
});
|
||||
|
||||
describe('downloadBlobs', () => {
|
||||
it('returns a joing promise mapping all files to download action', async () => {
|
||||
download.downloadFile = (file) => Promise.resolve(file.name);
|
||||
const responses = await download.downloadBlobs(files);
|
||||
expect(responses).toEqual(files.map((file) => file.name));
|
||||
let downloadFile;
|
||||
beforeEach(() => {
|
||||
downloadFile = download.downloadFile;
|
||||
download.downloadFile = jest.fn((file) => Promise.resolve(file.name));
|
||||
});
|
||||
afterEach(() => { download.downloadFile = downloadFile; });
|
||||
|
||||
it('returns a mapping of all files to download action', async () => {
|
||||
const downloadedBlobs = await download.downloadBlobs(files);
|
||||
expect(download.downloadFile).toHaveBeenCalledTimes(files.length);
|
||||
expect(downloadedBlobs.length).toEqual(files.length);
|
||||
expect(downloadedBlobs).toEqual(files.map(file => file.name));
|
||||
});
|
||||
|
||||
it('returns a mapping of errors from download action', () => {
|
||||
download.downloadFile = jest.fn(() => { throw new Error(); });
|
||||
expect(download.downloadBlobs(files)).rejects.toEqual(download.DownloadException(files.map(file => file.name)));
|
||||
expect(download.downloadFile).toHaveBeenCalledTimes(files.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('downloadFiles', () => {
|
||||
let downloadBlobs;
|
||||
beforeEach(() => {
|
||||
dispatch = jest.fn();
|
||||
selectors.grading.selected.response = () => ({ files });
|
||||
selectors.grading.selected.username = () => username;
|
||||
download.zipFiles = jest.fn();
|
||||
});
|
||||
it('dispatches network request with downloadFiles key', () => {
|
||||
|
||||
downloadBlobs = download.downloadBlobs;
|
||||
download.downloadBlobs = () => Promise.resolve(blobs);
|
||||
});
|
||||
afterEach(() => { download.downloadBlobs = downloadBlobs; });
|
||||
it('dispatches network request with downloadFiles key', () => {
|
||||
download.downloadFiles()(dispatch, getState);
|
||||
const { networkRequest } = dispatch.mock.calls[0][0];
|
||||
expect(networkRequest.requestKey).toEqual(RequestKeys.downloadFiles);
|
||||
});
|
||||
it('dispatches network request for downloadFiles, zipping output of downloadBlobs', () => {
|
||||
it('dispatches network request for downloadFiles, zipping output of downloadBlobs', async () => {
|
||||
download.downloadBlobs = () => Promise.resolve(blobs);
|
||||
download.downloadFiles()(dispatch, getState);
|
||||
const { networkRequest } = dispatch.mock.calls[0][0];
|
||||
networkRequest.promise.then(() => {
|
||||
expect(download.zipFiles).toHaveBeenCalledWith(files, blobs);
|
||||
});
|
||||
await networkRequest.promise;
|
||||
expect(download.zipFiles).toHaveBeenCalledWith(files, blobs, username);
|
||||
});
|
||||
it('throws an error on failure', () => {
|
||||
download.downloadBlobs = () => Promise.all([Promise.resolve(null)]);
|
||||
it('network request catch all of the errors', () => {
|
||||
const blobsErrors = ['arbitary', 'error'];
|
||||
download.downloadBlobs = () => Promise.reject(blobsErrors);
|
||||
|
||||
download.downloadFiles()(dispatch, getState);
|
||||
const { networkRequest } = dispatch.mock.calls[0][0];
|
||||
expect(networkRequest.promise).rejects.toThrow('Fetch failed');
|
||||
expect(networkRequest.promise).rejects.toEqual(blobsErrors);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -49,11 +49,8 @@ export const loadSubmission = () => (dispatch, getState) => {
|
||||
dispatch(actions.grading.loadSubmission({ ...response, submissionUUID }));
|
||||
if (selectors.grading.selected.isGrading(getState())) {
|
||||
dispatch(actions.app.setShowRubric(true));
|
||||
// safety constraints
|
||||
let { gradeData } = response;
|
||||
if (gradeData === null || gradeData === undefined || Object.keys(gradeData).length) {
|
||||
gradeData = selectors.app.emptyGrade(getState());
|
||||
}
|
||||
gradeData = selectors.app.fillGradeData(getState(), gradeData);
|
||||
const lockStatus = selectors.grading.selected.lockStatus(getState());
|
||||
dispatch(actions.grading.startGrading({ lockStatus, gradeData }));
|
||||
}
|
||||
@@ -76,10 +73,7 @@ export const startGrading = () => (dispatch, getState) => {
|
||||
onSuccess: (response) => {
|
||||
dispatch(actions.app.setShowRubric(true));
|
||||
let gradeData = selectors.grading.selected.gradeData(getState());
|
||||
// safety constraints
|
||||
if (gradeData === null || gradeData === undefined || Object.keys(gradeData).length) {
|
||||
gradeData = selectors.app.emptyGrade(getState());
|
||||
}
|
||||
gradeData = selectors.app.fillGradeData(getState(), gradeData);
|
||||
dispatch(actions.grading.startGrading({ ...response, gradeData }));
|
||||
},
|
||||
onFailure: (error) => {
|
||||
|
||||
@@ -10,7 +10,7 @@ jest.mock('./requests', () => ({
|
||||
}));
|
||||
|
||||
jest.mock('data/redux/app/selectors', () => ({
|
||||
emptyGrade: (state) => ({ emptyGrade: state }),
|
||||
fillGradeData: (state, data) => ({ fillGradeData: state, data }),
|
||||
}));
|
||||
|
||||
jest.mock('data/redux/grading/selectors', () => ({
|
||||
@@ -140,32 +140,22 @@ describe('grading thunkActions', () => {
|
||||
beforeEach(() => {
|
||||
dispatch.mockClear();
|
||||
});
|
||||
test('dispatches startGrading with selected gradeData if truthy', () => {
|
||||
const fillString = 'selectors.app.fillGradeData based on selected gradeData';
|
||||
test(`dispatches startGrading w/ ${fillString}`, () => {
|
||||
actionArgs.onSuccess(startResponse);
|
||||
expect(dispatch.mock.calls).toContainEqual([
|
||||
actions.app.setShowRubric(true),
|
||||
], [
|
||||
actions.grading.startGrading({
|
||||
...startResponse,
|
||||
gradeData: selectors.grading.selected.gradeData(testState),
|
||||
gradeData: selectors.app.fillGradeData(
|
||||
testState,
|
||||
selectors.grading.selected.gradeData(testState),
|
||||
),
|
||||
}),
|
||||
]);
|
||||
expect(dispatch.mock.calls).toContainEqual([actions.app.setShowRubric(true)]);
|
||||
});
|
||||
test('dispatches startGrading with empty grade if selected gradeData is null', () => {
|
||||
const emptyGrade = selectors.app.emptyGrade(testState);
|
||||
const expected = [
|
||||
actions.grading.startGrading({ ...startResponse, gradeData: emptyGrade }),
|
||||
];
|
||||
selectors.grading.selected.gradeData.mockReturnValue(null);
|
||||
actionArgs.onSuccess({ ...startResponse, gradeData: null });
|
||||
expect(dispatch.mock.calls).toContainEqual(expected);
|
||||
expect(dispatch.mock.calls).toContainEqual([actions.app.setShowRubric(true)]);
|
||||
dispatch.mockClear();
|
||||
actionArgs.onSuccess({ ...startResponse, gradeData: null });
|
||||
expect(dispatch.mock.calls).toContainEqual(expected);
|
||||
expect(dispatch.mock.calls).toContainEqual([actions.app.setShowRubric(true)]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -97,10 +97,13 @@ const unlockSubmission = (submissionUUID) => client().delete(
|
||||
* batchUnlockSubmissions(submissionUUIDs)
|
||||
* @param {string[]} submissionUUIDs - list of submission uuids
|
||||
*/
|
||||
const batchUnlockSubmissions = (submissionUUIDs) => {
|
||||
console.log({ batchUnlockSubmissions: submissionUUIDs });
|
||||
return new Promise(resolve => resolve());
|
||||
};
|
||||
const batchUnlockSubmissions = (submissionUUIDs) => post(
|
||||
stringifyUrl(
|
||||
urls.batchUnlockSubmissionsUrl,
|
||||
{ [paramKeys.oraLocation]: locationId },
|
||||
),
|
||||
{ submissionUUIDs },
|
||||
).then(response => response.data);
|
||||
|
||||
/*
|
||||
* post('api/updateGrade', { submissionUUID, gradeData })
|
||||
|
||||
@@ -10,6 +10,7 @@ const oraInitializeUrl = `${baseEsgUrl}initialize`;
|
||||
const fetchSubmissionUrl = `${baseEsgUrl}submission`;
|
||||
const fetchSubmissionStatusUrl = `${baseEsgUrl}submission/status`;
|
||||
const fetchSubmissionLockUrl = `${baseEsgUrl}submission/lock`;
|
||||
const batchUnlockSubmissionsUrl = `${baseEsgUrl}submission/batch/unlock`;
|
||||
const updateSubmissionGradeUrl = `${baseEsgUrl}submission/grade`;
|
||||
|
||||
const course = (courseId) => `${baseUrl}/courses/${courseId}`;
|
||||
@@ -25,6 +26,7 @@ export default StrictDict({
|
||||
fetchSubmissionUrl,
|
||||
fetchSubmissionStatusUrl,
|
||||
fetchSubmissionLockUrl,
|
||||
batchUnlockSubmissionsUrl,
|
||||
updateSubmissionGradeUrl,
|
||||
baseUrl,
|
||||
course,
|
||||
|
||||
@@ -10,16 +10,6 @@
|
||||
"ora-grading.ResponseDisplay.FileRenderer.fileNotFound": "File not found",
|
||||
"ora-grading.ResponseDisplay.FileRenderer.unknownError": "Unknown errors",
|
||||
"ora-grading.InfoPopover.alt-text": "Display more info",
|
||||
"learn.navigation.course.tabs.label": "Course Material",
|
||||
"header.menu.dashboard.label": "Dashboard",
|
||||
"header.help.label": "Help",
|
||||
"header.menu.profile.label": "Profile",
|
||||
"header.menu.account.label": "Account",
|
||||
"header.menu.orderHistory.label": "Order History",
|
||||
"header.navigation.skipNavLink": "Skip to main content.",
|
||||
"header.menu.signOut.label": "Sign Out",
|
||||
"header.register.sentenceCase": "Register",
|
||||
"header.signIn.sentenceCase": "Sign in",
|
||||
"ora-grading.CriterionFeedback.addCommentsLabel": "Add comments",
|
||||
"ora-grading.CriterionFeedback.commentsLabel": "Comments",
|
||||
"ora-grading.CriterionFeedback.optional": "(Optional)",
|
||||
@@ -53,6 +43,8 @@
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.downloading": "Downloading",
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.downloaded": "Downloaded!",
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.retryDownload": "Retry download",
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.submissionFile": "Submission Files",
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.fileSizeExceed": "Exceeded the allow download size",
|
||||
"ora-grading.ReviewActions.overrideConfirmTitle": "Are you sure you want to override this grade?",
|
||||
"ora-grading.ReviewActions.overrideConfirmWarning": "This cannot be undone. The learner may have already received their grade.",
|
||||
"ora-grading.ReviewActions.overrideConfirmContinue": "Continue grade override",
|
||||
@@ -93,6 +85,7 @@
|
||||
"ora-grading.ReviewModal.errorDownloadFailed": "Couldn't download files",
|
||||
"ora-grading.ReviewModal.errorDownloadFailedContent": "We're sorry, something went wrong when we tried to download these files. Please try again.",
|
||||
"ora-grading.ReviewModal.errorRetryDownload": "Retry download",
|
||||
"ora-grading.ReviewModal.errorDownloadFailedFiles": "Failed files:",
|
||||
"ora-grading.Rubric.gradeSubmitted": "Grade Submitted",
|
||||
"ora-grading.Rubric.rubric": "Rubric",
|
||||
"ora-grading.Rubric.submitGrade": "Submit grade",
|
||||
|
||||
@@ -10,16 +10,6 @@
|
||||
"ora-grading.ResponseDisplay.FileRenderer.fileNotFound": "Archivo no encontrado",
|
||||
"ora-grading.ResponseDisplay.FileRenderer.unknownError": "Errores desconocidos",
|
||||
"ora-grading.InfoPopover.alt-text": "Mostrar más información",
|
||||
"learn.navigation.course.tabs.label": "Material del Curso",
|
||||
"header.menu.dashboard.label": "Panel de Control",
|
||||
"header.help.label": "Ayuda",
|
||||
"header.menu.profile.label": "Perfil",
|
||||
"header.menu.account.label": "Cuenta",
|
||||
"header.menu.orderHistory.label": "Historial de órdenes",
|
||||
"header.navigation.skipNavLink": "Dirígete al contenido principal.",
|
||||
"header.menu.signOut.label": "Cerrar sesión",
|
||||
"header.register.sentenceCase": "Registrarse",
|
||||
"header.signIn.sentenceCase": "Iniciar sesión",
|
||||
"ora-grading.CriterionFeedback.addCommentsLabel": "Añadir comentarios",
|
||||
"ora-grading.CriterionFeedback.commentsLabel": "Comentarios",
|
||||
"ora-grading.CriterionFeedback.optional": "(Opcional)",
|
||||
@@ -53,6 +43,8 @@
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.downloading": "Descargando",
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.downloaded": "¡Descargado!",
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.retryDownload": "Vuelva a intentar descargar",
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.submissionFile": "Submission Files",
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.fileSizeExceed": "Exceeded the allow download size",
|
||||
"ora-grading.ReviewActions.overrideConfirmTitle": "¿Está seguro de que desea anular esta calificación?",
|
||||
"ora-grading.ReviewActions.overrideConfirmWarning": "Esto no se puede deshacer. Es posible que el alumno ya haya recibido su calificación.",
|
||||
"ora-grading.ReviewActions.overrideConfirmContinue": "Continuar anulación de calificación",
|
||||
@@ -93,6 +85,7 @@
|
||||
"ora-grading.ReviewModal.errorDownloadFailed": "No se pudieron descargar los archivos",
|
||||
"ora-grading.ReviewModal.errorDownloadFailedContent": "Lo sentimos, algo salió mal cuando intentamos descargar estos archivos. Inténtalo de nuevo.",
|
||||
"ora-grading.ReviewModal.errorRetryDownload": "Vuelva a intentar descargar",
|
||||
"ora-grading.ReviewModal.errorDownloadFailedFiles": "Failed files:",
|
||||
"ora-grading.Rubric.gradeSubmitted": "Calificación enviada",
|
||||
"ora-grading.Rubric.rubric": "Rúbrica",
|
||||
"ora-grading.Rubric.submitGrade": "Enviar calificación",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"ora-grading.demoAlert.warningMessage": "Grade submission is disabled in the Demo mode of the new ORA Staff Grader.",
|
||||
"ora-grading.demoAlert.confirm": "Confirm",
|
||||
"ora-grading.demoAlert.title": "Demo submit prevented",
|
||||
"ora-grading.demoAlert.warningMessage": "La soumission des notes est désactivée dans le mode démonstration du nouveau correcteur ORA.",
|
||||
"ora-grading.demoAlert.confirm": "Confirmer",
|
||||
"ora-grading.demoAlert.title": "Soumission de démonstration empêchée",
|
||||
"ora-grading.FilePopoverContent.filePopoverNameTitle": "Nom du fichier",
|
||||
"ora-grading.FilePopoverCellContent.filePopoverDescriptionTitle": "Description du fichier",
|
||||
"ora-grading.FilePopoverCellContent.fileSizeTitle": "Taille du fichier",
|
||||
@@ -10,24 +10,14 @@
|
||||
"ora-grading.ResponseDisplay.FileRenderer.fileNotFound": "Fichier introuvable",
|
||||
"ora-grading.ResponseDisplay.FileRenderer.unknownError": "Erreurs inconnues",
|
||||
"ora-grading.InfoPopover.alt-text": "Afficher plus d'informations",
|
||||
"learn.navigation.course.tabs.label": "Matériel de cours",
|
||||
"header.menu.dashboard.label": "Tableau de bord",
|
||||
"header.help.label": "Aide",
|
||||
"header.menu.profile.label": "Profil",
|
||||
"header.menu.account.label": "Compte",
|
||||
"header.menu.orderHistory.label": "Historique des commandes",
|
||||
"header.navigation.skipNavLink": "Passer au contenu principal",
|
||||
"header.menu.signOut.label": "Se déconnecter",
|
||||
"header.register.sentenceCase": "S'inscrire",
|
||||
"header.signIn.sentenceCase": "Se connecter.",
|
||||
"ora-grading.CriterionFeedback.addCommentsLabel": "Ajoutez des commentaires",
|
||||
"ora-grading.CriterionFeedback.commentsLabel": "Commentaires",
|
||||
"ora-grading.CriterionFeedback.optional": "(Optionnel)",
|
||||
"ora-grading.RadioCriterion.optionPoints": "{points} points",
|
||||
"ora-grading.RadioCriterion.rubricSelectedError": "La sélection de la rubrique est requise",
|
||||
"ora-grading.CriterionFeedback.criterionFeedbackError": "Le feedback est requis",
|
||||
"ora-grading.ReviewModal.demoHeading": "Demo Mode",
|
||||
"ora-grading.ReviewModal.demoMessage": "You are using the Demo Mode of the new Enhanced ORA Staff Grader interface. You will be unable to submit grades until you activate the feature.",
|
||||
"ora-grading.ReviewModal.demoHeading": "Mode de démonstration",
|
||||
"ora-grading.ReviewModal.demoMessage": "Vous utilisez le mode de démonstration de la nouvelle interface du nouveau correcteur ORA amélioré. Vous ne pourrez pas soumettre de notes tant que vous n'aurez pas activé la fonctionnalité.",
|
||||
"ora-grading.ListView.ListViewBreadcrumbs.backToResponses": "Retour à toutes les réponses ouvertes",
|
||||
"ora-grading.ListView.noResultsFoundTitle": "Rien ici encore",
|
||||
"ora-grading.ListView.noResultsFoundBody": "Lorsque les apprenants soumettront des réponses, elles apparaîtront ici",
|
||||
@@ -53,6 +43,8 @@
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.downloading": "Téléchargement",
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.downloaded": "Téléchargé !",
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.retryDownload": "Réessayez le téléchargement",
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.submissionFile": "Submission Files",
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.fileSizeExceed": "Exceeded the allow download size",
|
||||
"ora-grading.ReviewActions.overrideConfirmTitle": "Êtes-vous sûr de vouloir remplacer cette note ?",
|
||||
"ora-grading.ReviewActions.overrideConfirmWarning": "Ça ne peut pas être annulé. L'apprenant peut avoir déjà reçu sa note.",
|
||||
"ora-grading.ReviewActions.overrideConfirmContinue": "Continuer le remplacement de la note",
|
||||
@@ -76,7 +68,7 @@
|
||||
"ora-grading.ReviewModal.goBack": "Retour",
|
||||
"ora-grading.ReviewModal.CloseReviewConfirmModal.confirmText": "Fermer le modal",
|
||||
"ora-grading.ReviewModal.loadingResponse": "Chargement de la réponse",
|
||||
"ora-grading.ReviewModal.demoTitleMessage": "Grading Demo",
|
||||
"ora-grading.ReviewModal.demoTitleMessage": "Démonstration de correcteur",
|
||||
"ora-grading.ReviewModal.loadErrorHeading": "Erreur lors du chargement des soumissions",
|
||||
"ora-grading.ReviewModal.loadErrorMessage1": "Une erreur s'est produite lors du chargement de cette soumission. Essayez de recharger cette soumission.",
|
||||
"ora-grading.ReviewModal.reloadSubmission": "Recharger la soumission",
|
||||
@@ -93,6 +85,7 @@
|
||||
"ora-grading.ReviewModal.errorDownloadFailed": "Impossible de télécharger les fichiers",
|
||||
"ora-grading.ReviewModal.errorDownloadFailedContent": "Nous sommes désolés, une erreur s'est produite lorsque nous avons essayé de télécharger ces fichiers. Veuillez réessayer.",
|
||||
"ora-grading.ReviewModal.errorRetryDownload": "Réessayez le téléchargement",
|
||||
"ora-grading.ReviewModal.errorDownloadFailedFiles": "Fichiers ayant échoué :",
|
||||
"ora-grading.Rubric.gradeSubmitted": "Note soumise",
|
||||
"ora-grading.Rubric.rubric": "Rubrique",
|
||||
"ora-grading.Rubric.submitGrade": "Soumettre la note",
|
||||
|
||||
@@ -10,16 +10,6 @@
|
||||
"ora-grading.ResponseDisplay.FileRenderer.fileNotFound": "File not found",
|
||||
"ora-grading.ResponseDisplay.FileRenderer.unknownError": "Unknown errors",
|
||||
"ora-grading.InfoPopover.alt-text": "Display more info",
|
||||
"learn.navigation.course.tabs.label": "Course Material",
|
||||
"header.menu.dashboard.label": "Dashboard",
|
||||
"header.help.label": "Help",
|
||||
"header.menu.profile.label": "Profile",
|
||||
"header.menu.account.label": "Account",
|
||||
"header.menu.orderHistory.label": "Order History",
|
||||
"header.navigation.skipNavLink": "Skip to main content.",
|
||||
"header.menu.signOut.label": "Sign Out",
|
||||
"header.register.sentenceCase": "Register",
|
||||
"header.signIn.sentenceCase": "Sign in",
|
||||
"ora-grading.CriterionFeedback.addCommentsLabel": "Add comments",
|
||||
"ora-grading.CriterionFeedback.commentsLabel": "Comments",
|
||||
"ora-grading.CriterionFeedback.optional": "(Optional)",
|
||||
@@ -53,6 +43,8 @@
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.downloading": "Downloading",
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.downloaded": "Downloaded!",
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.retryDownload": "Retry download",
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.submissionFile": "Submission Files",
|
||||
"ora-grading.ResponseDisplay.SubmissionFiles.fileSizeExceed": "Exceeded the allow download size",
|
||||
"ora-grading.ReviewActions.overrideConfirmTitle": "Are you sure you want to override this grade?",
|
||||
"ora-grading.ReviewActions.overrideConfirmWarning": "This cannot be undone. The learner may have already received their grade.",
|
||||
"ora-grading.ReviewActions.overrideConfirmContinue": "Continue grade override",
|
||||
@@ -93,6 +85,7 @@
|
||||
"ora-grading.ReviewModal.errorDownloadFailed": "Couldn't download files",
|
||||
"ora-grading.ReviewModal.errorDownloadFailedContent": "We're sorry, something went wrong when we tried to download these files. Please try again.",
|
||||
"ora-grading.ReviewModal.errorRetryDownload": "Retry download",
|
||||
"ora-grading.ReviewModal.errorDownloadFailedFiles": "Failed files:",
|
||||
"ora-grading.Rubric.gradeSubmitted": "Grade Submitted",
|
||||
"ora-grading.Rubric.rubric": "Rubric",
|
||||
"ora-grading.Rubric.submitGrade": "Submit grade",
|
||||
|
||||
@@ -11,8 +11,12 @@ import {
|
||||
APP_INIT_ERROR,
|
||||
initialize,
|
||||
subscribe,
|
||||
mergeConfig,
|
||||
} from '@edx/frontend-platform';
|
||||
|
||||
import { messages as footerMessages } from '@edx/frontend-component-footer';
|
||||
import { messages as headerMesssages } from '@edx/frontend-component-header';
|
||||
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import messages from './i18n';
|
||||
@@ -38,8 +42,16 @@ subscribe(APP_INIT_ERROR, (error) => {
|
||||
});
|
||||
|
||||
initialize({
|
||||
handlers: {
|
||||
config: () => {
|
||||
mergeConfig({
|
||||
SUPPORT_URL: process.env.SUPPORT_URL || null,
|
||||
}, 'OraGradingAppConfig');
|
||||
},
|
||||
},
|
||||
messages: [
|
||||
messages,
|
||||
headerMesssages,
|
||||
footerMessages,
|
||||
],
|
||||
requireAuthenticatedUser: true,
|
||||
|
||||
@@ -6,7 +6,9 @@ import {
|
||||
initialize,
|
||||
subscribe,
|
||||
} from '@edx/frontend-platform';
|
||||
|
||||
import { messages as footerMessages } from '@edx/frontend-component-footer';
|
||||
import { messages as headerMesssages } from '@edx/frontend-component-header';
|
||||
|
||||
import appMessages from './i18n';
|
||||
import App from './App';
|
||||
@@ -36,17 +38,18 @@ describe('app registry', () => {
|
||||
afterAll(() => {
|
||||
window.document.getElementById = getElement;
|
||||
});
|
||||
|
||||
test('subscribe is called for APP_READY, linking App to root element', () => {
|
||||
const callArgs = subscribe.mock.calls[0];
|
||||
const callArgs = subscribe.mock.calls[1];
|
||||
expect(callArgs[0]).toEqual(APP_READY);
|
||||
expect(callArgs[1]()).toEqual(
|
||||
ReactDOM.render(<App />, document.getElementById('root')),
|
||||
);
|
||||
});
|
||||
test('initialize is called with footerMessages and requireAuthenticatedUser', () => {
|
||||
expect(initialize).toHaveBeenCalledWith({
|
||||
messages: [appMessages, footerMessages],
|
||||
requireAuthenticatedUser: true,
|
||||
});
|
||||
expect(initialize).toHaveBeenCalledTimes(1);
|
||||
const initializeArg = initialize.mock.calls[0][0];
|
||||
expect(initializeArg.messages).toEqual([appMessages, headerMesssages, footerMessages]);
|
||||
expect(initializeArg.requireAuthenticatedUser).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import InfoPopover from 'components/InfoPopover/messages';
|
||||
import ResponseDisplay from 'containers/ResponseDisplay/messages';
|
||||
import ResponseDisplayComponents from 'containers/ResponseDisplay/components/messages';
|
||||
import CourseHeader from 'containers/CourseHeader/messages';
|
||||
import CriterionContainer from 'containers/CriterionContainer/messages';
|
||||
import ListView from 'containers/ListView/messages';
|
||||
import ReviewActions from 'containers/ReviewActions/messages';
|
||||
@@ -20,7 +19,6 @@ export default {
|
||||
InfoPopover: mapMessages(InfoPopover),
|
||||
ResponseDisplay: mapMessages(ResponseDisplay),
|
||||
ResponseDisplayComponents: mapMessages(ResponseDisplayComponents),
|
||||
CourseHeader: mapMessages(CourseHeader),
|
||||
CriterionContainer: mapMessages(CriterionContainer),
|
||||
ListView: mapMessages(ListView),
|
||||
ReviewActions: mapMessages(ReviewActions),
|
||||
|
||||
Reference in New Issue
Block a user