Compare commits
2 Commits
master
...
bw/compone
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
096ec432fe | ||
|
|
18648eb8c6 |
131
src/components/GradebookHeader/__snapshots__/index.test.jsx.snap
Normal file
131
src/components/GradebookHeader/__snapshots__/index.test.jsx.snap
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`GradebookHeader component render default view shapshot 1`] = `
|
||||||
|
<div
|
||||||
|
className="gradebook-header"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
className="mb-3"
|
||||||
|
href="test-dashboard-url"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<<
|
||||||
|
</span>
|
||||||
|
Back to Dashboard
|
||||||
|
</a>
|
||||||
|
<h1>
|
||||||
|
Gradebook
|
||||||
|
</h1>
|
||||||
|
<div
|
||||||
|
className="subtitle-row d-flex justify-content-between align-items-center"
|
||||||
|
>
|
||||||
|
<h2>
|
||||||
|
test-course-id
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`GradebookHeader component render frozen grades snapshot: show frozen warning 1`] = `
|
||||||
|
<div
|
||||||
|
className="gradebook-header"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
className="mb-3"
|
||||||
|
href="test-dashboard-url"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<<
|
||||||
|
</span>
|
||||||
|
Back to Dashboard
|
||||||
|
</a>
|
||||||
|
<h1>
|
||||||
|
Gradebook
|
||||||
|
</h1>
|
||||||
|
<div
|
||||||
|
className="subtitle-row d-flex justify-content-between align-items-center"
|
||||||
|
>
|
||||||
|
<h2>
|
||||||
|
test-course-id
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="alert alert-warning"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
The grades for this course are now frozen. Editing of grades is no longer allowed.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`GradebookHeader component render show bulk management snapshot: show toggle view message button with handleToggleViewClick method 1`] = `
|
||||||
|
<div
|
||||||
|
className="gradebook-header"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
className="mb-3"
|
||||||
|
href="test-dashboard-url"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<<
|
||||||
|
</span>
|
||||||
|
Back to Dashboard
|
||||||
|
</a>
|
||||||
|
<h1>
|
||||||
|
Gradebook
|
||||||
|
</h1>
|
||||||
|
<div
|
||||||
|
className="subtitle-row d-flex justify-content-between align-items-center"
|
||||||
|
>
|
||||||
|
<h2>
|
||||||
|
test-course-id
|
||||||
|
</h2>
|
||||||
|
<Button
|
||||||
|
onClick={[MockFunction hooks.handleToggleViewClick]}
|
||||||
|
variant="tertiary"
|
||||||
|
>
|
||||||
|
toggle-view-message
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`GradebookHeader component render user cannot view gradebook snapshot: show unauthorized warning 1`] = `
|
||||||
|
<div
|
||||||
|
className="gradebook-header"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
className="mb-3"
|
||||||
|
href="test-dashboard-url"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<<
|
||||||
|
</span>
|
||||||
|
Back to Dashboard
|
||||||
|
</a>
|
||||||
|
<h1>
|
||||||
|
Gradebook
|
||||||
|
</h1>
|
||||||
|
<div
|
||||||
|
className="subtitle-row d-flex justify-content-between align-items-center"
|
||||||
|
>
|
||||||
|
<h2>
|
||||||
|
test-course-id
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="alert alert-warning"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
You are not authorized to view the gradebook for this course.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
35
src/components/GradebookHeader/hooks.js
Normal file
35
src/components/GradebookHeader/hooks.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { views } from 'data/constants/app';
|
||||||
|
import { actions, selectors } from 'data/redux/hooks';
|
||||||
|
|
||||||
|
import messages from './messages';
|
||||||
|
|
||||||
|
export const useGradebookHeaderData = () => {
|
||||||
|
const activeView = selectors.app.useActiveView();
|
||||||
|
const courseId = selectors.app.useCourseId();
|
||||||
|
const areGradesFrozen = selectors.assignmentTypes.useAreGradesFrozen();
|
||||||
|
const canUserViewGradebook = selectors.roles.useCanUserViewGradebook();
|
||||||
|
const showBulkManagement = selectors.root.useShowBulkManagement();
|
||||||
|
const setView = actions.app.useSetView();
|
||||||
|
|
||||||
|
const handleToggleViewClick = () => setView(
|
||||||
|
activeView === views.grades
|
||||||
|
? views.bulkManagementHistory
|
||||||
|
: views.grades,
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleViewMessage = activeView === views.grades
|
||||||
|
? messages.toActivityLog
|
||||||
|
: messages.toGradesView;
|
||||||
|
|
||||||
|
return {
|
||||||
|
areGradesFrozen,
|
||||||
|
canUserViewGradebook,
|
||||||
|
courseId,
|
||||||
|
showBulkManagement,
|
||||||
|
|
||||||
|
handleToggleViewClick,
|
||||||
|
toggleViewMessage,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useGradebookHeaderData;
|
||||||
90
src/components/GradebookHeader/hooks.test.js
Normal file
90
src/components/GradebookHeader/hooks.test.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { views } from 'data/constants/app';
|
||||||
|
import { actions, selectors } from 'data/redux/hooks';
|
||||||
|
|
||||||
|
import messages from './messages';
|
||||||
|
import useGradebookHeaderData from './hooks';
|
||||||
|
|
||||||
|
jest.mock('data/redux/hooks', () => ({
|
||||||
|
actions: {
|
||||||
|
app: {
|
||||||
|
useSetView: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
app: {
|
||||||
|
useActiveView: jest.fn(),
|
||||||
|
useCourseId: jest.fn(),
|
||||||
|
},
|
||||||
|
assignmentTypes: {
|
||||||
|
useAreGradesFrozen: jest.fn(),
|
||||||
|
},
|
||||||
|
roles: {
|
||||||
|
useCanUserViewGradebook: jest.fn(),
|
||||||
|
},
|
||||||
|
root: {
|
||||||
|
useShowBulkManagement: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const activeView = 'test-active-view';
|
||||||
|
selectors.app.useActiveView.mockReturnValue(activeView);
|
||||||
|
const courseId = 'test-course-id';
|
||||||
|
selectors.app.useCourseId.mockReturnValue(courseId);
|
||||||
|
const areGradesFrozen = 'test-are-grades-frozen';
|
||||||
|
selectors.assignmentTypes.useAreGradesFrozen.mockReturnValue(areGradesFrozen);
|
||||||
|
const canUserViewGradebook = 'test-can-user-view-gradebook';
|
||||||
|
selectors.roles.useCanUserViewGradebook.mockReturnValue(canUserViewGradebook);
|
||||||
|
const showBulkManagement = 'test-show-bulk-management';
|
||||||
|
selectors.root.useShowBulkManagement.mockReturnValue(showBulkManagement);
|
||||||
|
|
||||||
|
const setView = jest.fn();
|
||||||
|
actions.app.useSetView.mockReturnValue(setView);
|
||||||
|
|
||||||
|
let out;
|
||||||
|
describe('useGradebookHeaderData hooks', () => {
|
||||||
|
describe('initialization', () => {
|
||||||
|
it('initializes redux hooks', () => {
|
||||||
|
out = useGradebookHeaderData();
|
||||||
|
expect(selectors.app.useActiveView).toHaveBeenCalled();
|
||||||
|
expect(selectors.app.useCourseId).toHaveBeenCalled();
|
||||||
|
expect(selectors.assignmentTypes.useAreGradesFrozen).toHaveBeenCalled();
|
||||||
|
expect(selectors.roles.useCanUserViewGradebook).toHaveBeenCalled();
|
||||||
|
expect(selectors.root.useShowBulkManagement).toHaveBeenCalled();
|
||||||
|
expect(actions.app.useSetView).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('output', () => {
|
||||||
|
test('redux fields', () => {
|
||||||
|
out = useGradebookHeaderData();
|
||||||
|
expect(out.areGradesFrozen).toEqual(areGradesFrozen);
|
||||||
|
expect(out.canUserViewGradebook).toEqual(canUserViewGradebook);
|
||||||
|
expect(out.courseId).toEqual(courseId);
|
||||||
|
expect(out.showBulkManagement).toEqual(showBulkManagement);
|
||||||
|
});
|
||||||
|
describe('handleToggleViewClick', () => {
|
||||||
|
it('calls setView with bulkManagemnetHistory message if grades view is active', () => {
|
||||||
|
selectors.app.useActiveView.mockReturnValueOnce(views.grades);
|
||||||
|
out = useGradebookHeaderData();
|
||||||
|
out.handleToggleViewClick();
|
||||||
|
expect(setView).toHaveBeenCalledWith(views.bulkManagementHistory);
|
||||||
|
});
|
||||||
|
it('calls setView with grades view if grades view is not active', () => {
|
||||||
|
out = useGradebookHeaderData();
|
||||||
|
out.handleToggleViewClick();
|
||||||
|
expect(setView).toHaveBeenCalledWith(views.grades);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('toggleViewMessage', () => {
|
||||||
|
it('returns toActivityLog message if grades view is active', () => {
|
||||||
|
selectors.app.useActiveView.mockReturnValueOnce(views.grades);
|
||||||
|
out = useGradebookHeaderData();
|
||||||
|
expect(out.toggleViewMessage).toEqual(messages.toActivityLog);
|
||||||
|
});
|
||||||
|
it('returns toGradesView message if grades view is not active', () => {
|
||||||
|
out = useGradebookHeaderData();
|
||||||
|
expect(out.toggleViewMessage).toEqual(messages.toGradesView);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,106 +1,50 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
|
||||||
import { Button } from '@edx/paragon';
|
import { Button } from '@edx/paragon';
|
||||||
|
|
||||||
import { views } from 'data/constants/app';
|
import { instructorDashboardUrl } from 'data/services/lms/urls';
|
||||||
import actions from 'data/actions';
|
import useGradebookHeaderData from './hooks';
|
||||||
import selectors from 'data/selectors';
|
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
export class GradebookHeader extends React.Component {
|
export const GradebookHeader = () => {
|
||||||
constructor(props) {
|
const { formatMessage } = useIntl();
|
||||||
super(props);
|
const {
|
||||||
this.handleToggleViewClick = this.handleToggleViewClick.bind(this);
|
areGradesFrozen,
|
||||||
}
|
canUserViewGradebook,
|
||||||
|
courseId,
|
||||||
handleToggleViewClick() {
|
handleToggleViewClick,
|
||||||
const newView = this.props.activeView === views.grades ? views.bulkManagementHistory : views.grades;
|
showBulkManagement,
|
||||||
this.props.setView(newView);
|
toggleViewMessage,
|
||||||
}
|
} = useGradebookHeaderData();
|
||||||
|
const dashboardUrl = instructorDashboardUrl();
|
||||||
get toggleViewMessage() {
|
return (
|
||||||
return this.props.activeView === views.grades
|
<div className="gradebook-header">
|
||||||
? messages.toActivityLog
|
<a href={dashboardUrl} className="mb-3">
|
||||||
: messages.toGradesView;
|
<span aria-hidden="true">{'<< '}</span>
|
||||||
}
|
{formatMessage(messages.backToDashboard)}
|
||||||
|
</a>
|
||||||
lmsInstructorDashboardUrl = courseId => (
|
<h1>{formatMessage(messages.gradebook)}</h1>
|
||||||
`${getConfig().LMS_BASE_URL}/courses/${courseId}/instructor`
|
<div className="subtitle-row d-flex justify-content-between align-items-center">
|
||||||
);
|
<h2>{courseId}</h2>
|
||||||
|
{showBulkManagement && (
|
||||||
render() {
|
<Button variant="tertiary" onClick={handleToggleViewClick}>
|
||||||
return (
|
{formatMessage(toggleViewMessage)}
|
||||||
<div className="gradebook-header">
|
</Button>
|
||||||
<a
|
|
||||||
href={this.lmsInstructorDashboardUrl(this.props.courseId)}
|
|
||||||
className="mb-3"
|
|
||||||
>
|
|
||||||
<span aria-hidden="true">{'<< '}</span>
|
|
||||||
<FormattedMessage {...messages.backToDashboard} />
|
|
||||||
</a>
|
|
||||||
<h1>
|
|
||||||
<FormattedMessage {...messages.gradebook} />
|
|
||||||
</h1>
|
|
||||||
<div className="subtitle-row d-flex justify-content-between align-items-center">
|
|
||||||
<h2>{this.props.courseId}</h2>
|
|
||||||
{ this.props.showBulkManagement && (
|
|
||||||
<Button
|
|
||||||
variant="tertiary"
|
|
||||||
onClick={this.handleToggleViewClick}
|
|
||||||
>
|
|
||||||
<FormattedMessage {...this.toggleViewMessage} />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{this.props.areGradesFrozen
|
|
||||||
&& (
|
|
||||||
<div className="alert alert-warning" role="alert">
|
|
||||||
<FormattedMessage {...messages.frozenWarning} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{(this.props.canUserViewGradebook === false) && (
|
|
||||||
<div className="alert alert-warning" role="alert">
|
|
||||||
<FormattedMessage {...messages.unauthorizedWarning} />
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
{areGradesFrozen && (
|
||||||
}
|
<div className="alert alert-warning" role="alert">
|
||||||
}
|
{formatMessage(messages.frozenWarning)}
|
||||||
|
</div>
|
||||||
GradebookHeader.defaultProps = {
|
)}
|
||||||
// redux
|
{(canUserViewGradebook === false) && (
|
||||||
courseId: '',
|
<div className="alert alert-warning" role="alert">
|
||||||
areGradesFrozen: false,
|
{formatMessage(messages.unauthorizedWarning)}
|
||||||
canUserViewGradebook: false,
|
</div>
|
||||||
showBulkManagement: false,
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
GradebookHeader.propTypes = {
|
export default GradebookHeader;
|
||||||
// redux
|
|
||||||
activeView: PropTypes.string.isRequired,
|
|
||||||
courseId: PropTypes.string,
|
|
||||||
areGradesFrozen: PropTypes.bool,
|
|
||||||
canUserViewGradebook: PropTypes.bool,
|
|
||||||
setView: PropTypes.func.isRequired,
|
|
||||||
showBulkManagement: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const mapStateToProps = (state) => ({
|
|
||||||
activeView: selectors.app.activeView(state),
|
|
||||||
courseId: selectors.app.courseId(state),
|
|
||||||
areGradesFrozen: selectors.assignmentTypes.areGradesFrozen(state),
|
|
||||||
canUserViewGradebook: selectors.roles.canUserViewGradebook(state),
|
|
||||||
showBulkManagement: selectors.root.showBulkManagement(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const mapDispatchToProps = {
|
|
||||||
setView: actions.app.setView,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(GradebookHeader);
|
|
||||||
|
|||||||
77
src/components/GradebookHeader/index.test.jsx
Normal file
77
src/components/GradebookHeader/index.test.jsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
import { Button } from '@edx/paragon';
|
||||||
|
|
||||||
|
import { formatMessage } from 'testUtils';
|
||||||
|
import { instructorDashboardUrl } from 'data/services/lms/urls';
|
||||||
|
|
||||||
|
import useGradebookHeaderData from './hooks';
|
||||||
|
import GradebookHeader from '.';
|
||||||
|
|
||||||
|
jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
|
||||||
|
jest.mock('data/services/lms/urls', () => ({
|
||||||
|
instructorDashboardUrl: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
instructorDashboardUrl.mockReturnValue('test-dashboard-url');
|
||||||
|
|
||||||
|
const hookProps = {
|
||||||
|
areGradesFrozen: false,
|
||||||
|
canUserViewGradebook: true,
|
||||||
|
courseId: 'test-course-id',
|
||||||
|
handleToggleViewClick: jest.fn().mockName('hooks.handleToggleViewClick'),
|
||||||
|
showBulkManagement: false,
|
||||||
|
toggleViewMessage: { defaultMessage: 'toggle-view-message' },
|
||||||
|
};
|
||||||
|
useGradebookHeaderData.mockReturnValue(hookProps);
|
||||||
|
|
||||||
|
let el;
|
||||||
|
describe('GradebookHeader component', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
el = shallow(<GradebookHeader />);
|
||||||
|
});
|
||||||
|
describe('behavior', () => {
|
||||||
|
it('initializes hooks', () => {
|
||||||
|
expect(useGradebookHeaderData).toHaveBeenCalledWith();
|
||||||
|
expect(useIntl).toHaveBeenCalledWith();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('render', () => {
|
||||||
|
describe('default view', () => {
|
||||||
|
test('shapshot', () => {
|
||||||
|
expect(el).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('show bulk management', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
useGradebookHeaderData.mockReturnValueOnce({ ...hookProps, showBulkManagement: true });
|
||||||
|
el = shallow(<GradebookHeader />);
|
||||||
|
});
|
||||||
|
test('snapshot: show toggle view message button with handleToggleViewClick method', () => {
|
||||||
|
expect(el).toMatchSnapshot();
|
||||||
|
const { onClick, children } = el.find(Button).props();
|
||||||
|
expect(onClick).toEqual(hookProps.handleToggleViewClick);
|
||||||
|
expect(children).toEqual(formatMessage(hookProps.toggleViewMessage));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('frozen grades', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
useGradebookHeaderData.mockReturnValueOnce({ ...hookProps, areGradesFrozen: true });
|
||||||
|
el = shallow(<GradebookHeader />);
|
||||||
|
});
|
||||||
|
test('snapshot: show frozen warning', () => {
|
||||||
|
expect(el).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('user cannot view gradebook', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
useGradebookHeaderData.mockReturnValueOnce({ ...hookProps, canUserViewGradebook: false });
|
||||||
|
el = shallow(<GradebookHeader />);
|
||||||
|
});
|
||||||
|
test('snapshot: show unauthorized warning', () => {
|
||||||
|
expect(el).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { shallow } from 'enzyme';
|
|
||||||
|
|
||||||
import { Button } from '@edx/paragon';
|
|
||||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
|
||||||
|
|
||||||
import actions from 'data/actions';
|
|
||||||
import selectors from 'data/selectors';
|
|
||||||
import { views } from 'data/constants/app';
|
|
||||||
import messages from './messages';
|
|
||||||
import { GradebookHeader, mapDispatchToProps, mapStateToProps } from '.';
|
|
||||||
|
|
||||||
jest.mock('@edx/paragon', () => ({
|
|
||||||
Button: () => 'Button',
|
|
||||||
}));
|
|
||||||
jest.mock('@edx/frontend-platform/i18n', () => ({
|
|
||||||
defineMessages: m => m,
|
|
||||||
FormattedMessage: () => 'FormattedMessage',
|
|
||||||
}));
|
|
||||||
jest.mock('data/actions', () => ({
|
|
||||||
__esModule: true,
|
|
||||||
default: {
|
|
||||||
app: { setView: jest.fn() },
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
jest.mock('data/selectors', () => ({
|
|
||||||
__esModule: true,
|
|
||||||
default: {
|
|
||||||
app: {
|
|
||||||
activeView: jest.fn(state => ({ aciveView: state })),
|
|
||||||
courseId: jest.fn(state => ({ courseId: state })),
|
|
||||||
},
|
|
||||||
assignmentTypes: { areGradesFrozen: jest.fn(state => ({ areGradesFrozen: state })) },
|
|
||||||
roles: { canUserViewGradebook: jest.fn(state => ({ canUserViewGradebook: state })) },
|
|
||||||
root: { showBulkManagement: jest.fn(state => ({ showBulkManagement: state })) },
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const courseId = 'fakeID';
|
|
||||||
describe('GradebookHeader component', () => {
|
|
||||||
const props = {
|
|
||||||
activeView: views.grades,
|
|
||||||
areGradesFrozen: false,
|
|
||||||
canUserViewGradebook: false,
|
|
||||||
courseId,
|
|
||||||
showBulkManagement: false,
|
|
||||||
};
|
|
||||||
beforeEach(() => {
|
|
||||||
props.setView = jest.fn();
|
|
||||||
});
|
|
||||||
describe('snapshots', () => {
|
|
||||||
let el;
|
|
||||||
beforeEach(() => {
|
|
||||||
el = shallow(<GradebookHeader {...props} />);
|
|
||||||
el.instance().handleToggleViewClick = jest.fn().mockName('this.handleToggleViewClick');
|
|
||||||
});
|
|
||||||
describe('default values (grades frozen, cannot view).', () => {
|
|
||||||
test('unauthorized warning, but no grades frozen warning', () => {
|
|
||||||
expect(el.instance().render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('grades frozen, cannot view', () => {
|
|
||||||
test('unauthorized warning, and grades frozen warning.', () => {
|
|
||||||
el.setProps({ areGradesFrozen: true });
|
|
||||||
expect(el.instance().render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('grades frozen, can view.', () => {
|
|
||||||
test('grades frozen warning but no unauthorized warning', () => {
|
|
||||||
el.setProps({ areGradesFrozen: true, canUserViewGradebook: true });
|
|
||||||
expect(el.instance().render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('show bulk management, active view is grades view', () => {
|
|
||||||
test('toggle view button to activity log', () => {
|
|
||||||
el.setProps({ showBulkManagement: true });
|
|
||||||
expect(el.find(Button).getElement()).toEqual((
|
|
||||||
<Button
|
|
||||||
variant="tertiary"
|
|
||||||
onClick={el.instance().handleToggleViewClick}
|
|
||||||
>
|
|
||||||
<FormattedMessage {...messages.toActivityLog} />
|
|
||||||
</Button>
|
|
||||||
));
|
|
||||||
expect(el.instance().render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('show bulk management, active view is bulkManagementHistory view', () => {
|
|
||||||
test('toggle view button to grades', () => {
|
|
||||||
el.setProps({ showBulkManagement: true, activeView: views.bulkManagementHistory });
|
|
||||||
expect(el.find(Button).getElement()).toEqual((
|
|
||||||
<Button
|
|
||||||
variant="tertiary"
|
|
||||||
onClick={el.instance().handleToggleViewClick}
|
|
||||||
>
|
|
||||||
<FormattedMessage {...messages.toGradesView} />
|
|
||||||
</Button>
|
|
||||||
));
|
|
||||||
expect(el.instance().render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('behavior', () => {
|
|
||||||
let el;
|
|
||||||
beforeEach(() => {
|
|
||||||
el = shallow(<GradebookHeader {...props} />);
|
|
||||||
});
|
|
||||||
describe('handleToggleViewClick', () => {
|
|
||||||
test('calls setView with activity view if activeView is grades', () => {
|
|
||||||
el.instance().handleToggleViewClick();
|
|
||||||
expect(props.setView).toHaveBeenCalledWith(views.bulkManagementHistory);
|
|
||||||
});
|
|
||||||
test('calls setView with grades view if activeView is bulkManagementHistory', () => {
|
|
||||||
el.setProps({ activeView: views.bulkManagementHistory });
|
|
||||||
el.instance().handleToggleViewClick();
|
|
||||||
expect(props.setView).toHaveBeenCalledWith(views.grades);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('mapStateToProps', () => {
|
|
||||||
let mapped;
|
|
||||||
const testState = { a: 'test', example: 'state' };
|
|
||||||
beforeEach(() => {
|
|
||||||
mapped = mapStateToProps(testState);
|
|
||||||
});
|
|
||||||
test('activeView from app.activeView', () => {
|
|
||||||
expect(mapped.activeView).toEqual(selectors.app.activeView(testState));
|
|
||||||
});
|
|
||||||
test('courseId from app.courseId', () => {
|
|
||||||
expect(mapped.courseId).toEqual(selectors.app.courseId(testState));
|
|
||||||
});
|
|
||||||
test('areGradesFrozen from assignmentTypes selector', () => {
|
|
||||||
expect(
|
|
||||||
mapped.areGradesFrozen,
|
|
||||||
).toEqual(selectors.assignmentTypes.areGradesFrozen(testState));
|
|
||||||
});
|
|
||||||
test('canUserViewGradebook from roles selector', () => {
|
|
||||||
expect(
|
|
||||||
mapped.canUserViewGradebook,
|
|
||||||
).toEqual(selectors.roles.canUserViewGradebook(testState));
|
|
||||||
});
|
|
||||||
test('showBulkManagement from root showBulkManagement selector', () => {
|
|
||||||
expect(mapped.showBulkManagement).toEqual(selectors.root.showBulkManagement(testState));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('mapDispatchToProps', () => {
|
|
||||||
test('setView from actions.app.setView', () => {
|
|
||||||
expect(mapDispatchToProps.setView).toEqual(actions.app.setView);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
/* eslint-disable react/sort-comp, react/button-has-type */
|
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import { views } from 'data/constants/app';
|
|
||||||
import actions from 'data/actions';
|
|
||||||
import selectors from 'data/selectors';
|
|
||||||
|
|
||||||
import NetworkButton from 'components/NetworkButton';
|
|
||||||
import ImportGradesButton from './ImportGradesButton';
|
|
||||||
|
|
||||||
import messages from './BulkManagementControls.messages';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <BulkManagementControls />
|
|
||||||
* Provides download buttons for Bulk Management and Intervention reports, only if
|
|
||||||
* showBulkManagement is set in redus.
|
|
||||||
*/
|
|
||||||
export class BulkManagementControls extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.handleClickExportGrades = this.handleClickExportGrades.bind(this);
|
|
||||||
this.handleViewActivityLog = this.handleViewActivityLog.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClickExportGrades() {
|
|
||||||
this.props.downloadBulkGradesReport();
|
|
||||||
window.location.assign(this.props.gradeExportUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleViewActivityLog() {
|
|
||||||
this.props.setView(views.bulkManagementHistory);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return this.props.showBulkManagement && (
|
|
||||||
<div className="d-flex">
|
|
||||||
<NetworkButton
|
|
||||||
label={messages.downloadGradesBtn}
|
|
||||||
onClick={this.handleClickExportGrades}
|
|
||||||
/>
|
|
||||||
<ImportGradesButton />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BulkManagementControls.defaultProps = {
|
|
||||||
showBulkManagement: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
BulkManagementControls.propTypes = {
|
|
||||||
// redux
|
|
||||||
downloadBulkGradesReport: PropTypes.func.isRequired,
|
|
||||||
gradeExportUrl: PropTypes.string.isRequired,
|
|
||||||
showBulkManagement: PropTypes.bool,
|
|
||||||
setView: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const mapStateToProps = (state) => ({
|
|
||||||
gradeExportUrl: selectors.root.gradeExportUrl(state),
|
|
||||||
showBulkManagement: selectors.root.showBulkManagement(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const mapDispatchToProps = {
|
|
||||||
downloadBulkGradesReport: actions.grades.downloadReport.bulkGrades,
|
|
||||||
setView: actions.app.setView,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(BulkManagementControls);
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { shallow } from 'enzyme';
|
|
||||||
|
|
||||||
import actions from 'data/actions';
|
|
||||||
import selectors from 'data/selectors';
|
|
||||||
import { views } from 'data/constants/app';
|
|
||||||
|
|
||||||
import {
|
|
||||||
BulkManagementControls,
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps,
|
|
||||||
} from './BulkManagementControls';
|
|
||||||
|
|
||||||
jest.mock('./ImportGradesButton', () => 'ImportGradesButton');
|
|
||||||
jest.mock('components/NetworkButton', () => 'NetworkButton');
|
|
||||||
jest.mock('data/selectors', () => ({
|
|
||||||
__esModule: true,
|
|
||||||
default: {
|
|
||||||
root: {
|
|
||||||
gradeExportUrl: (state) => ({ gradeExportUrl: state }),
|
|
||||||
interventionExportUrl: (state) => ({ interventionExportUrl: state }),
|
|
||||||
showBulkManagement: (state) => ({ showBulkManagement: state }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
jest.mock('data/actions', () => ({
|
|
||||||
__esModule: true,
|
|
||||||
default: {
|
|
||||||
app: { setView: jest.fn() },
|
|
||||||
grades: {
|
|
||||||
downloadReport: {
|
|
||||||
bulkGrades: jest.fn(),
|
|
||||||
intervention: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('BulkManagementControls', () => {
|
|
||||||
describe('component', () => {
|
|
||||||
let el;
|
|
||||||
let props = {
|
|
||||||
gradeExportUrl: 'gradesGoHere',
|
|
||||||
interventionExportUrl: 'interventionsGoHere',
|
|
||||||
};
|
|
||||||
beforeEach(() => {
|
|
||||||
props = {
|
|
||||||
...props,
|
|
||||||
downloadBulkGradesReport: jest.fn(),
|
|
||||||
downloadInterventionReport: jest.fn(),
|
|
||||||
setView: jest.fn(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
test('snapshot - empty if showBulkManagement is not truthy', () => {
|
|
||||||
expect(shallow(<BulkManagementControls {...props} />)).toEqual({});
|
|
||||||
});
|
|
||||||
describe('behavior', () => {
|
|
||||||
const oldWindowLocation = window.location;
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
delete window.location;
|
|
||||||
window.location = Object.defineProperties(
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
...Object.getOwnPropertyDescriptors(oldWindowLocation),
|
|
||||||
assign: {
|
|
||||||
configurable: true,
|
|
||||||
value: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
beforeEach(() => {
|
|
||||||
window.location.assign.mockReset();
|
|
||||||
el = shallow(<BulkManagementControls {...props} showBulkManagement />);
|
|
||||||
});
|
|
||||||
afterAll(() => {
|
|
||||||
// restore `window.location` to the `jsdom` `Location` object
|
|
||||||
window.location = oldWindowLocation;
|
|
||||||
});
|
|
||||||
describe('handleViewActivityLog', () => {
|
|
||||||
it('calls props.setView(views.bulkManagementHistory)', () => {
|
|
||||||
el.instance().handleViewActivityLog();
|
|
||||||
expect(props.setView).toHaveBeenCalledWith(views.bulkManagementHistory);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('handleClickExportGrades', () => {
|
|
||||||
const assertions = [
|
|
||||||
'calls props.downloadBulkGradesReport',
|
|
||||||
'sets location to props.gradeExportUrl',
|
|
||||||
];
|
|
||||||
it(assertions.join(' and '), () => {
|
|
||||||
el.instance().handleClickExportGrades();
|
|
||||||
expect(props.downloadBulkGradesReport).toHaveBeenCalled();
|
|
||||||
expect(window.location.assign).toHaveBeenCalledWith(props.gradeExportUrl);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('mapStateToProps', () => {
|
|
||||||
let mapped;
|
|
||||||
const testState = { do: 'not', test: 'me' };
|
|
||||||
beforeEach(() => {
|
|
||||||
mapped = mapStateToProps(testState);
|
|
||||||
});
|
|
||||||
test('gradeExportUrl from root.gradeExportUrl', () => {
|
|
||||||
expect(mapped.gradeExportUrl).toEqual(selectors.root.gradeExportUrl(testState));
|
|
||||||
});
|
|
||||||
test('showBulkManagement from root.showBulkManagement', () => {
|
|
||||||
expect(mapped.showBulkManagement).toEqual(selectors.root.showBulkManagement(testState));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('mapDispatchToProps', () => {
|
|
||||||
test('downloadBulkGradesReport from actions.grades.downloadReport.bulkGrades', () => {
|
|
||||||
expect(
|
|
||||||
mapDispatchToProps.downloadBulkGradesReport,
|
|
||||||
).toEqual(actions.grades.downloadReport.bulkGrades);
|
|
||||||
});
|
|
||||||
test('setView from actions.app.setView', () => {
|
|
||||||
expect(mapDispatchToProps.setView).toEqual(actions.app.setView);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`BulkManagementControls render snapshot - show - network and import buttons 1`] = `
|
||||||
|
<div
|
||||||
|
className="d-flex"
|
||||||
|
>
|
||||||
|
<NetworkButton
|
||||||
|
label={
|
||||||
|
Object {
|
||||||
|
"defaultMessage": "Download Grades",
|
||||||
|
"description": "A labeled button that allows an admin user to download course grades all at once (in bulk).",
|
||||||
|
"id": "gradebook.GradesView.BulkManagementControls.bulkManagementLabel",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onClick={[MockFunction]}
|
||||||
|
/>
|
||||||
|
<ImportGradesButton />
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
18
src/components/GradesView/BulkManagementControls/hooks.js
Normal file
18
src/components/GradesView/BulkManagementControls/hooks.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { actions, selectors } from 'data/redux/hooks';
|
||||||
|
|
||||||
|
export const useBulkManagementControlsData = () => {
|
||||||
|
const gradeExportUrl = selectors.root.useGradeExportUrl();
|
||||||
|
const showBulkManagement = selectors.root.useShowBulkManagement();
|
||||||
|
const downloadBulkGradesReport = actions.grades.downloadReport.useBulkGrades();
|
||||||
|
|
||||||
|
const handleClickExportGrades = () => {
|
||||||
|
downloadBulkGradesReport();
|
||||||
|
window.location.assign(gradeExportUrl);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: showBulkManagement,
|
||||||
|
handleClickExportGrades,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export default useBulkManagementControlsData;
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { actions, selectors } from 'data/redux/hooks';
|
||||||
|
|
||||||
|
import useBulkManagementControlsData from './hooks';
|
||||||
|
|
||||||
|
jest.mock('data/redux/hooks', () => ({
|
||||||
|
actions: {
|
||||||
|
grades: {
|
||||||
|
downloadReport: { useBulkGrades: jest.fn() },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
root: {
|
||||||
|
useGradeExportUrl: jest.fn(),
|
||||||
|
useShowBulkManagement: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const downloadBulkGrades = jest.fn();
|
||||||
|
actions.grades.downloadReport.useBulkGrades.mockReturnValue(downloadBulkGrades);
|
||||||
|
const gradeExportUrl = 'test-grade-export-url';
|
||||||
|
selectors.root.useGradeExportUrl.mockReturnValue(gradeExportUrl);
|
||||||
|
selectors.root.useShowBulkManagement.mockReturnValue(true);
|
||||||
|
|
||||||
|
let hook;
|
||||||
|
describe('useBulkManagementControlsData', () => {
|
||||||
|
const oldWindowLocation = window.location;
|
||||||
|
beforeAll(() => {
|
||||||
|
delete window.location;
|
||||||
|
window.location = Object.defineProperties(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
...Object.getOwnPropertyDescriptors(oldWindowLocation),
|
||||||
|
assign: { configurable: true, value: jest.fn() },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
beforeEach(() => {
|
||||||
|
window.location.assign.mockReset();
|
||||||
|
hook = useBulkManagementControlsData();
|
||||||
|
});
|
||||||
|
afterAll(() => {
|
||||||
|
// restore `window.location` to the `jsdom` `Location` object
|
||||||
|
window.location = oldWindowLocation;
|
||||||
|
});
|
||||||
|
describe('initialization', () => {
|
||||||
|
it('initializes redux hooks', () => {
|
||||||
|
expect(selectors.root.useGradeExportUrl).toHaveBeenCalledWith();
|
||||||
|
expect(selectors.root.useShowBulkManagement).toHaveBeenCalledWith();
|
||||||
|
expect(actions.grades.downloadReport.useBulkGrades).toHaveBeenCalledWith();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('output', () => {
|
||||||
|
it('forwards show from showBulkManagement', () => {
|
||||||
|
expect(hook.show).toEqual(true);
|
||||||
|
selectors.root.useShowBulkManagement.mockReturnValue(false);
|
||||||
|
hook = useBulkManagementControlsData();
|
||||||
|
expect(hook.show).toEqual(false);
|
||||||
|
});
|
||||||
|
describe('handleClickExportGrades', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
hook.handleClickExportGrades();
|
||||||
|
});
|
||||||
|
it('downloads bulk grades report', () => {
|
||||||
|
expect(downloadBulkGrades).toHaveBeenCalledWith();
|
||||||
|
});
|
||||||
|
it('sets window location to grade export url', () => {
|
||||||
|
expect(window.location.assign).toHaveBeenCalledWith(gradeExportUrl);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
32
src/components/GradesView/BulkManagementControls/index.jsx
Normal file
32
src/components/GradesView/BulkManagementControls/index.jsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/* eslint-disable react/sort-comp, react/button-has-type */
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import NetworkButton from 'components/NetworkButton';
|
||||||
|
import ImportGradesButton from '../ImportGradesButton';
|
||||||
|
|
||||||
|
import useBulkManagementControlsData from './hooks';
|
||||||
|
import messages from './messages';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <BulkManagementControls />
|
||||||
|
* Provides download buttons for Bulk Management and Intervention reports, only if
|
||||||
|
* showBulkManagement is set in redus.
|
||||||
|
*/
|
||||||
|
export const BulkManagementControls = () => {
|
||||||
|
const {
|
||||||
|
show,
|
||||||
|
handleClickExportGrades,
|
||||||
|
} = useBulkManagementControlsData();
|
||||||
|
|
||||||
|
return show && (
|
||||||
|
<div className="d-flex">
|
||||||
|
<NetworkButton
|
||||||
|
label={messages.downloadGradesBtn}
|
||||||
|
onClick={handleClickExportGrades}
|
||||||
|
/>
|
||||||
|
<ImportGradesButton />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BulkManagementControls;
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
|
import useBulkManagementControlsData from './hooks';
|
||||||
|
import BulkManagementControls from '.';
|
||||||
|
|
||||||
|
jest.mock('../ImportGradesButton', () => 'ImportGradesButton');
|
||||||
|
jest.mock('components/NetworkButton', () => 'NetworkButton');
|
||||||
|
|
||||||
|
jest.mock('./hooks', () => jest.fn());
|
||||||
|
|
||||||
|
const hookProps = {
|
||||||
|
show: true,
|
||||||
|
handleClickExportGrades: jest.fn(),
|
||||||
|
};
|
||||||
|
useBulkManagementControlsData.mockReturnValue(hookProps);
|
||||||
|
|
||||||
|
describe('BulkManagementControls', () => {
|
||||||
|
describe('behavior', () => {
|
||||||
|
shallow(<BulkManagementControls />);
|
||||||
|
expect(useBulkManagementControlsData).toHaveBeenCalledWith();
|
||||||
|
});
|
||||||
|
describe('render', () => {
|
||||||
|
test('snapshot - show - network and import buttons', () => {
|
||||||
|
expect(shallow(<BulkManagementControls />)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
test('snapshot - empty if show is not truthy', () => {
|
||||||
|
useBulkManagementControlsData.mockReturnValueOnce({ ...hookProps, show: false });
|
||||||
|
expect(shallow(<BulkManagementControls />).isEmptyRender()).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -4,6 +4,7 @@ import { actionHook } from './utils';
|
|||||||
|
|
||||||
const app = StrictDict({
|
const app = StrictDict({
|
||||||
useSetLocalFilter: actionHook(actions.app.setLocalFilter),
|
useSetLocalFilter: actionHook(actions.app.setLocalFilter),
|
||||||
|
useSetView: actionHook(actions.app.setView),
|
||||||
});
|
});
|
||||||
|
|
||||||
const filters = StrictDict({
|
const filters = StrictDict({
|
||||||
@@ -16,7 +17,14 @@ const filters = StrictDict({
|
|||||||
useUpdateTrack: actionHook(actions.filters.update.track),
|
useUpdateTrack: actionHook(actions.filters.update.track),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const grades = StrictDict({
|
||||||
|
downloadReport: {
|
||||||
|
useBulkGrades: actionHook(actions.grades.downloadReport.bulkGrades),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default StrictDict({
|
export default StrictDict({
|
||||||
app,
|
app,
|
||||||
filters,
|
filters,
|
||||||
|
grades,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import actionHooks from './actions';
|
|||||||
jest.mock('data/actions', () => ({
|
jest.mock('data/actions', () => ({
|
||||||
app: {
|
app: {
|
||||||
setLocalFilter: jest.fn(),
|
setLocalFilter: jest.fn(),
|
||||||
|
setView: jest.fn(),
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
update: {
|
update: {
|
||||||
@@ -14,6 +15,11 @@ jest.mock('data/actions', () => ({
|
|||||||
assignmentLimits: jest.fn(),
|
assignmentLimits: jest.fn(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
grades: {
|
||||||
|
downloadReport: {
|
||||||
|
bulkGrades: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
jest.mock('./utils', () => ({
|
jest.mock('./utils', () => ({
|
||||||
actionHook: (action) => ({ actionHook: action }),
|
actionHook: (action) => ({ actionHook: action }),
|
||||||
@@ -32,6 +38,7 @@ describe('action hooks', () => {
|
|||||||
const hookKeys = keyStore(actionHooks.app);
|
const hookKeys = keyStore(actionHooks.app);
|
||||||
beforeEach(() => { hooks = actionHooks.app; });
|
beforeEach(() => { hooks = actionHooks.app; });
|
||||||
testActionHook(hookKeys.useSetLocalFilter, actions.app.setLocalFilter);
|
testActionHook(hookKeys.useSetLocalFilter, actions.app.setLocalFilter);
|
||||||
|
testActionHook(hookKeys.useSetView, actions.app.setView);
|
||||||
});
|
});
|
||||||
describe('filters', () => {
|
describe('filters', () => {
|
||||||
const hookKeys = keyStore(actionHooks.filters);
|
const hookKeys = keyStore(actionHooks.filters);
|
||||||
@@ -47,4 +54,11 @@ describe('action hooks', () => {
|
|||||||
);
|
);
|
||||||
testActionHook(hookKeys.useUpdateTrack, actionGroup.updateTrack);
|
testActionHook(hookKeys.useUpdateTrack, actionGroup.updateTrack);
|
||||||
});
|
});
|
||||||
|
describe('grades', () => {
|
||||||
|
beforeEach(() => { hooks = actionHooks.grades; });
|
||||||
|
test('downloadReport.useBulkGrades', () => {
|
||||||
|
expect(hooks.downloadReport.useBulkGrades)
|
||||||
|
.toEqual(actionHook(actions.grades.downloadReport.bulkGrades));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,16 +7,20 @@ export const root = StrictDict({
|
|||||||
useGradeExportUrl: () => useSelector(selectors.root.gradeExportUrl),
|
useGradeExportUrl: () => useSelector(selectors.root.gradeExportUrl),
|
||||||
useSelectedCohortEntry: () => useSelector(selectors.root.selectedCohortEntry),
|
useSelectedCohortEntry: () => useSelector(selectors.root.selectedCohortEntry),
|
||||||
useSelectedTrackEntry: () => useSelector(selectors.root.selectedTrackEntry),
|
useSelectedTrackEntry: () => useSelector(selectors.root.selectedTrackEntry),
|
||||||
|
useShowBulkManagement: () => useSelector(selectors.root.showBulkManagement),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const app = StrictDict({
|
export const app = StrictDict({
|
||||||
|
useActiveView: () => useSelector(selectors.app.activeView),
|
||||||
useAssignmentGradeLimits: () => useSelector(selectors.app.assignmentGradeLimits),
|
useAssignmentGradeLimits: () => useSelector(selectors.app.assignmentGradeLimits),
|
||||||
useAreCourseGradeFiltersValid: () => useSelector(selectors.app.areCourseGradeFiltersValid),
|
useAreCourseGradeFiltersValid: () => useSelector(selectors.app.areCourseGradeFiltersValid),
|
||||||
useCourseGradeLimits: () => useSelector(selectors.app.courseGradeLimits),
|
useCourseGradeLimits: () => useSelector(selectors.app.courseGradeLimits),
|
||||||
|
useCourseId: () => useSelector(selectors.app.courseId),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const assignmentTypes = StrictDict({
|
export const assignmentTypes = StrictDict({
|
||||||
useAllAssignmentTypes: () => useSelector(selectors.assignmentTypes.allAssignmentTypes),
|
useAllAssignmentTypes: () => useSelector(selectors.assignmentTypes.allAssignmentTypes),
|
||||||
|
useAreGradesFrozen: () => useSelector(selectors.assignmentTypes.areGradesFrozen),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const cohorts = StrictDict({
|
export const cohorts = StrictDict({
|
||||||
@@ -33,6 +37,10 @@ export const filters = StrictDict({
|
|||||||
useAssignmentType: () => useSelector(selectors.filters.assignmentType),
|
useAssignmentType: () => useSelector(selectors.filters.assignmentType),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const roles = StrictDict({
|
||||||
|
useCanUserViewGradebook: () => useSelector(selectors.roles.canUserViewGradebook),
|
||||||
|
});
|
||||||
|
|
||||||
export const tracks = StrictDict({
|
export const tracks = StrictDict({
|
||||||
useAllTracks: () => useSelector(selectors.tracks.allTracks),
|
useAllTracks: () => useSelector(selectors.tracks.allTracks),
|
||||||
// maybe not needed?
|
// maybe not needed?
|
||||||
@@ -44,6 +52,7 @@ export default StrictDict({
|
|||||||
assignmentTypes,
|
assignmentTypes,
|
||||||
cohorts,
|
cohorts,
|
||||||
filters,
|
filters,
|
||||||
|
roles,
|
||||||
tracks,
|
tracks,
|
||||||
root,
|
root,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,11 +9,16 @@ jest.mock('react-redux', () => ({
|
|||||||
|
|
||||||
jest.mock('data/selectors', () => ({
|
jest.mock('data/selectors', () => ({
|
||||||
app: {
|
app: {
|
||||||
|
activeView: jest.fn(),
|
||||||
assignmentGradeLimits: jest.fn(),
|
assignmentGradeLimits: jest.fn(),
|
||||||
areCourseGradeFiltersValid: jest.fn(),
|
areCourseGradeFiltersValid: jest.fn(),
|
||||||
courseGradelimits: jest.fn(),
|
courseGradelimits: jest.fn(),
|
||||||
|
courseId: jest.fn(),
|
||||||
|
},
|
||||||
|
assignmentTypes: {
|
||||||
|
allAssignmentTypes: jest.fn(),
|
||||||
|
areGradesFrozen: jest.fn(),
|
||||||
},
|
},
|
||||||
assignmentTypes: { allAssignmentTypes: jest.fn() },
|
|
||||||
cohorts: {
|
cohorts: {
|
||||||
allCohorts: jest.fn(),
|
allCohorts: jest.fn(),
|
||||||
cohortsByName: jest.fn(),
|
cohortsByName: jest.fn(),
|
||||||
@@ -25,6 +30,9 @@ jest.mock('data/selectors', () => ({
|
|||||||
selectedAssignmentLabel: jest.fn(),
|
selectedAssignmentLabel: jest.fn(),
|
||||||
assignmentType: jest.fn(),
|
assignmentType: jest.fn(),
|
||||||
},
|
},
|
||||||
|
roles: {
|
||||||
|
canUserViewGradebook: jest.fn(),
|
||||||
|
},
|
||||||
tracks: {
|
tracks: {
|
||||||
allTracks: jest.fn(),
|
allTracks: jest.fn(),
|
||||||
tracksByName: jest.fn(),
|
tracksByName: jest.fn(),
|
||||||
@@ -33,6 +41,7 @@ jest.mock('data/selectors', () => ({
|
|||||||
gradeExportUrl: jest.fn(),
|
gradeExportUrl: jest.fn(),
|
||||||
selectedCohortEntry: jest.fn(),
|
selectedCohortEntry: jest.fn(),
|
||||||
selectedTrackEntry: jest.fn(),
|
selectedTrackEntry: jest.fn(),
|
||||||
|
showBulkManagement: jest.fn(),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -49,20 +58,24 @@ describe('selector hooks', () => {
|
|||||||
testHook(hookKeys.useGradeExportUrl, selectors.root.gradeExportUrl);
|
testHook(hookKeys.useGradeExportUrl, selectors.root.gradeExportUrl);
|
||||||
testHook(hookKeys.useSelectedCohortEntry, selectors.root.selectedCohortEntry);
|
testHook(hookKeys.useSelectedCohortEntry, selectors.root.selectedCohortEntry);
|
||||||
testHook(hookKeys.useSelectedTrackEntry, selectors.root.selectedTrackEntry);
|
testHook(hookKeys.useSelectedTrackEntry, selectors.root.selectedTrackEntry);
|
||||||
|
testHook(hookKeys.useShowBulkManagement, selectors.root.showBulkManagement);
|
||||||
});
|
});
|
||||||
describe('app', () => {
|
describe('app', () => {
|
||||||
const hookKeys = keyStore(selectorHooks.app);
|
const hookKeys = keyStore(selectorHooks.app);
|
||||||
const selGroup = selectors.app;
|
const selGroup = selectors.app;
|
||||||
beforeEach(() => { hooks = selectorHooks.app; });
|
beforeEach(() => { hooks = selectorHooks.app; });
|
||||||
|
testHook(hookKeys.useActiveView, selGroup.activeView);
|
||||||
testHook(hookKeys.useAssignmentGradeLimits, selGroup.assignmentGradeLimits);
|
testHook(hookKeys.useAssignmentGradeLimits, selGroup.assignmentGradeLimits);
|
||||||
testHook(hookKeys.useAreCourseGradeFiltersValid, selGroup.areCourseGradeFiltersValid);
|
testHook(hookKeys.useAreCourseGradeFiltersValid, selGroup.areCourseGradeFiltersValid);
|
||||||
testHook(hookKeys.useCourseGradeLimits, selGroup.courseGradeLimits);
|
testHook(hookKeys.useCourseGradeLimits, selGroup.courseGradeLimits);
|
||||||
|
testHook(hookKeys.useCourseId, selGroup.courseId);
|
||||||
});
|
});
|
||||||
describe('assignmentTypes', () => {
|
describe('assignmentTypes', () => {
|
||||||
const hookKeys = keyStore(selectorHooks.assignmentTypes);
|
const hookKeys = keyStore(selectorHooks.assignmentTypes);
|
||||||
const selGroup = selectors.assignmentTypes;
|
const selGroup = selectors.assignmentTypes;
|
||||||
beforeEach(() => { hooks = selectorHooks.assignmentTypes; });
|
beforeEach(() => { hooks = selectorHooks.assignmentTypes; });
|
||||||
testHook(hookKeys.useAllAssignmentTypes, selGroup.allAssignmentTypes);
|
testHook(hookKeys.useAllAssignmentTypes, selGroup.allAssignmentTypes);
|
||||||
|
testHook(hookKeys.useAreGradesFrozen, selGroup.areGradesFrozen);
|
||||||
});
|
});
|
||||||
describe('cohorts', () => {
|
describe('cohorts', () => {
|
||||||
const hookKeys = keyStore(selectorHooks.cohorts);
|
const hookKeys = keyStore(selectorHooks.cohorts);
|
||||||
@@ -81,6 +94,12 @@ describe('selector hooks', () => {
|
|||||||
testHook(hookKeys.useSelectedAssignmentLabel, selGroup.selectedAssignmentLabel);
|
testHook(hookKeys.useSelectedAssignmentLabel, selGroup.selectedAssignmentLabel);
|
||||||
testHook(hookKeys.useAssignmentType, selGroup.assignmentType);
|
testHook(hookKeys.useAssignmentType, selGroup.assignmentType);
|
||||||
});
|
});
|
||||||
|
describe('roles', () => {
|
||||||
|
const hookKeys = keyStore(selectorHooks.roles);
|
||||||
|
const selGroup = selectors.roles;
|
||||||
|
beforeEach(() => { hooks = selectorHooks.roles; });
|
||||||
|
testHook(hookKeys.useCanUserViewGradebook, selGroup.canUserViewGradebook);
|
||||||
|
});
|
||||||
describe('tracks', () => {
|
describe('tracks', () => {
|
||||||
const hookKeys = keyStore(selectorHooks.tracks);
|
const hookKeys = keyStore(selectorHooks.tracks);
|
||||||
const selGroup = selectors.tracks;
|
const selGroup = selectors.tracks;
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ export const sectionOverrideHistoryUrl = (subsectionId, userId) => stringifyUrl(
|
|||||||
{ user_id: userId, history_record_limit: historyRecordLimit },
|
{ user_id: userId, history_record_limit: historyRecordLimit },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const instructorDashboardUrl = () => (
|
||||||
|
`${getConfig().LMS_BASE_URL}/courses/${courseId}/instructor`
|
||||||
|
);
|
||||||
|
|
||||||
export default StrictDict({
|
export default StrictDict({
|
||||||
getUrlPrefix,
|
getUrlPrefix,
|
||||||
getBulkGradesUrl,
|
getBulkGradesUrl,
|
||||||
|
|||||||
Reference in New Issue
Block a user