diff --git a/src/components/GradesView/FilterMenuToggle.jsx b/src/components/GradesView/FilterMenuToggle.jsx
deleted file mode 100644
index 9261709..0000000
--- a/src/components/GradesView/FilterMenuToggle.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-
-import { Button, Icon } from '@edx/paragon';
-import { FormattedMessage } from '@edx/frontend-platform/i18n';
-
-import thunkActions from 'data/thunkActions';
-
-import messages from './FilterMenuToggle.messages';
-
-/**
- * Controls for filtering the GradebookTable. Contains the "Edit Filters" button for opening the filter drawer
- * as well as the search box for searching by username/email.
- */
-export const FilterMenuToggle = ({ toggleFilterDrawer }) => (
-
-);
-
-FilterMenuToggle.propTypes = {
- // From Redux
- toggleFilterDrawer: PropTypes.func.isRequired,
-};
-
-export const mapStateToProps = () => ({});
-
-export const mapDispatchToProps = {
- toggleFilterDrawer: thunkActions.app.filterMenu.toggle,
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(FilterMenuToggle);
diff --git a/src/components/GradesView/FilterMenuToggle.messages.js b/src/components/GradesView/FilterMenuToggle.messages.js
deleted file mode 100644
index d028502..0000000
--- a/src/components/GradesView/FilterMenuToggle.messages.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import { defineMessages } from '@edx/frontend-platform/i18n';
-
-const messages = defineMessages({
- editFilters: {
- id: 'gradebook.GradesView.editFilterLabel',
- defaultMessage: 'Edit Filters',
- description: 'A labeled button in the Grades tab that opens/closes the Filters tab, allowing the grades to be filtered',
- },
-});
-
-export default messages;
diff --git a/src/components/GradesView/FilterMenuToggle.test.jsx b/src/components/GradesView/FilterMenuToggle.test.jsx
deleted file mode 100644
index 4d02fca..0000000
--- a/src/components/GradesView/FilterMenuToggle.test.jsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-
-import thunkActions from 'data/thunkActions';
-
-import { FilterMenuToggle, mapDispatchToProps, mapStateToProps } from './FilterMenuToggle';
-
-jest.mock('@edx/paragon', () => ({
- Button: () => 'Button',
- Icon: () => 'Icon',
-}));
-jest.mock('data/thunkActions', () => ({
- __esModule: true,
- default: {
- app: {
- filterMenu: { toggle: jest.fn() },
- },
- },
-}));
-
-describe('FilterMenuToggle component', () => {
- describe('snapshots', () => {
- test('basic snapshot', () => {
- const toggleFilterDrawer = jest.fn().mockName('this.props.toggleFilterDrawer');
- expect(shallow((
-
- ))).toMatchSnapshot();
- });
- });
- describe('mapStateToProps', () => {
- test('does not connect any selectors', () => {
- expect(mapStateToProps({ test: 'state' })).toEqual({});
- });
- });
- describe('mapDispatchToProps', () => {
- test('toggleFilterDrawer from thunkActions.app.filterMenu.toggle', () => {
- expect(mapDispatchToProps.toggleFilterDrawer).toEqual(
- thunkActions.app.filterMenu.toggle,
- );
- });
- });
-});
diff --git a/src/components/GradesView/FilteredUsersLabel.jsx b/src/components/GradesView/FilteredUsersLabel.jsx
deleted file mode 100644
index 3752dee..0000000
--- a/src/components/GradesView/FilteredUsersLabel.jsx
+++ /dev/null
@@ -1,44 +0,0 @@
-/* eslint-disable react/sort-comp, react/button-has-type, import/no-named-as-default */
-import React from 'react';
-import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-
-import { FormattedMessage } from '@edx/frontend-platform/i18n';
-
-import selectors from 'data/selectors';
-
-/**
- *
- * Simple label component displaying the filtered and total users shown
- */
-export const FilteredUsersLabel = ({
- filteredUsersCount,
- totalUsersCount,
-}) => {
- if (!totalUsersCount) {
- return null;
- }
- const bold = (val) => ({val});
- return (
-
- );
-};
-FilteredUsersLabel.propTypes = {
- filteredUsersCount: PropTypes.number.isRequired,
- totalUsersCount: PropTypes.number.isRequired,
-};
-
-export const mapStateToProps = (state) => ({
- totalUsersCount: selectors.grades.totalUsersCount(state),
- filteredUsersCount: selectors.grades.filteredUsersCount(state),
-});
-
-export default connect(mapStateToProps)(FilteredUsersLabel);
diff --git a/src/components/GradesView/FilteredUsersLabel.test.jsx b/src/components/GradesView/FilteredUsersLabel.test.jsx
deleted file mode 100644
index c6ec280..0000000
--- a/src/components/GradesView/FilteredUsersLabel.test.jsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-
-import selectors from 'data/selectors';
-import { FilteredUsersLabel, mapStateToProps } from './FilteredUsersLabel';
-
-jest.mock('@edx/paragon', () => ({
- Icon: () => 'Icon',
-}));
-jest.mock('data/selectors', () => ({
- __esModule: true,
- default: {
- grades: {
- filteredUsersCount: state => ({ filteredUsersCount: state }),
- totalUsersCount: state => ({ totalUsersCount: state }),
- },
- },
-}));
-
-describe('FilteredUsersLabel', () => {
- describe('component', () => {
- const props = {
- filteredUsersCount: 23,
- totalUsersCount: 140,
- };
- it('does not render if totalUsersCount is falsey', () => {
- expect(shallow()).toEqual({});
- });
- test('snapshot - displays label with number of filtered users out of total', () => {
- expect(shallow()).toMatchSnapshot();
- });
- });
- describe('mapStateToProps', () => {
- const testState = { a: 'nice', day: 'for', some: 'rain' };
- let mapped;
- beforeEach(() => {
- mapped = mapStateToProps(testState);
- });
- test('filteredUsersCount from grades.filteredUsersCount', () => {
- expect(mapped.filteredUsersCount).toEqual(selectors.grades.filteredUsersCount(testState));
- });
- test('totalUsersCount from grades.totalUsersCount', () => {
- expect(mapped.totalUsersCount).toEqual(selectors.grades.totalUsersCount(testState));
- });
- });
-});
diff --git a/src/components/GradesView/ImportGradesButton/hooks.test.js b/src/components/GradesView/ImportGradesButton/hooks.test.js
index 753dd09..c4439d0 100644
--- a/src/components/GradesView/ImportGradesButton/hooks.test.js
+++ b/src/components/GradesView/ImportGradesButton/hooks.test.js
@@ -29,7 +29,7 @@ testFormData.append('csv', testFile);
const ref = {
current: { click: jest.fn(), files: [testFile], value: 'test-value' },
};
-describe('useAssignmentFilterData hook', () => {
+describe('useImportButtonData hook', () => {
beforeEach(() => {
jest.clearAllMocks();
React.useRef.mockReturnValue(ref);
diff --git a/src/components/GradesView/ImportSuccessToast.jsx b/src/components/GradesView/ImportSuccessToast.jsx
deleted file mode 100644
index a38a2e0..0000000
--- a/src/components/GradesView/ImportSuccessToast.jsx
+++ /dev/null
@@ -1,72 +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 { Toast } from '@edx/paragon';
-import {
- injectIntl,
- intlShape,
-} from '@edx/frontend-platform/i18n';
-
-import selectors from 'data/selectors';
-import actions from 'data/actions';
-import { views } from 'data/constants/app';
-import messages from './ImportSuccessToast.messages';
-
-/**
- *
- * Toast component triggered by successful grade upload.
- * Provides a link to view the Bulk Management History tab.
- */
-export class ImportSuccessToast extends React.Component {
- constructor(props) {
- super(props);
- this.onClose = this.onClose.bind(this);
- this.handleShowHistoryView = this.handleShowHistoryView.bind(this);
- }
-
- onClose() {
- this.props.setShow(false);
- }
-
- handleShowHistoryView() {
- this.props.setAppView(views.bulkManagementHistory);
- this.onClose();
- }
-
- render() {
- return (
-
- {this.props.intl.formatMessage(messages.description)}
-
- );
- }
-}
-
-ImportSuccessToast.propTypes = {
- // injected
- intl: intlShape.isRequired,
- // redux
- show: PropTypes.bool.isRequired,
- setAppView: PropTypes.func.isRequired,
- setShow: PropTypes.func.isRequired,
-};
-
-export const mapStateToProps = (state) => ({
- show: selectors.app.showImportSuccessToast(state),
-});
-
-export const mapDispatchToProps = {
- setAppView: actions.app.setView,
- setShow: actions.app.setShowImportSuccessToast,
-};
-
-export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ImportSuccessToast));
diff --git a/src/components/GradesView/ImportSuccessToast.messages.js b/src/components/GradesView/ImportSuccessToast.messages.js
deleted file mode 100644
index 90791f6..0000000
--- a/src/components/GradesView/ImportSuccessToast.messages.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import { defineMessages } from '@edx/frontend-platform/i18n';
-
-const messages = defineMessages({
- description: {
- id: 'gradebook.GradesView.ImportSuccessToast.description',
- defaultMessage: 'Import Successful! Grades will be updated momentarily.',
- description: 'A message congratulating a successful Import of grades',
- },
- showHistoryViewBtn: {
- id: 'gradebook.GradesView.ImportSuccessToast.showHistoryViewBtn',
- defaultMessage: 'View Activity Log',
- description: 'The text on a button that loads a view of the Bulk Management Activity Log',
- },
-});
-
-export default messages;
diff --git a/src/components/GradesView/ImportSuccessToast.test.jsx b/src/components/GradesView/ImportSuccessToast.test.jsx
deleted file mode 100644
index be13492..0000000
--- a/src/components/GradesView/ImportSuccessToast.test.jsx
+++ /dev/null
@@ -1,110 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-
-import selectors from 'data/selectors';
-import actions from 'data/actions';
-import { views } from 'data/constants/app';
-
-import {
- ImportSuccessToast,
- mapStateToProps,
- mapDispatchToProps,
-} from './ImportSuccessToast';
-import messages from './ImportSuccessToast.messages';
-
-jest.mock('@edx/paragon', () => ({
- Toast: () => 'Toast',
-}));
-jest.mock('data/selectors', () => ({
- __esModule: true,
- default: {
- app: {
- showImportSuccessToast: (state) => ({ showImportSuccessToast: state }),
- },
- },
-}));
-jest.mock('data/actions', () => ({
- __esModule: true,
- default: {
- app: {
- setView: jest.fn(),
- setShow: jest.fn(),
- },
- },
-}));
-
-describe('ImportSuccessToast component', () => {
- describe('snapshots', () => {
- let el;
- let props = {
- show: true,
- };
- beforeEach(() => {
- props = {
- ...props,
- intl: { formatMessage: (msg) => msg.defaultMessage },
- setAppView: jest.fn(),
- setShow: jest.fn(),
- };
- el = shallow();
- });
- test('snapshot', () => {
- el.instance().handleShowHistoryView = jest.fn().mockName('handleShowHistoryView');
- el.instance().onClose = jest.fn().mockName('onClose');
- expect(el).toMatchSnapshot();
- });
- describe('Toast props', () => {
- let toastProps;
- beforeEach(() => {
- toastProps = el.props();
- });
- test('action has translated label and onClick from this.handleShowHistoryView', () => {
- expect(toastProps.action).toEqual({
- label: props.intl.formatMessage(messages.showHistoryViewBtn),
- onClick: el.instance().handleShowHistoryView,
- });
- });
- test('onClose from this.onClose method', () => {
- expect(toastProps.onClose).toEqual(el.instance().onClose);
- });
- test('show from show prop', () => {
- expect(toastProps.show).toEqual(props.show);
- el.setProps({ show: false });
- expect(el.props().show).toEqual(false);
- });
- });
- describe('onClose', () => {
- it('calls props.setShow(false)', () => {
- el.instance().onClose();
- expect(props.setShow).toHaveBeenCalledWith(false);
- });
- });
- describe('handleShowHistoryView', () => {
- it('calls setAppView with views.bulkManagementHistory and this.onClose', () => {
- el.instance().onClose = jest.fn();
- el.instance().handleShowHistoryView();
- expect(props.setAppView).toHaveBeenCalledWith(views.bulkManagementHistory);
- expect(el.instance().onClose).toHaveBeenCalled();
- });
- });
- });
- describe('behavior', () => {
- });
- describe('mapStateToProps', () => {
- const testState = { somewhere: 'over', the: 'rainbow' };
- const mapped = mapStateToProps(testState);
- test('show from app showImportSuccessToast selector', () => {
- expect(mapped.show).toEqual(
- selectors.app.showImportSuccessToast(testState),
- );
- });
- });
- describe('mapDispatchToProps', () => {
- test('setAppView from actions.app.setView', () => {
- expect(mapDispatchToProps.setAppView).toEqual(actions.app.setView);
- });
- test('setShow from actions.setShowImportSuccessToast', () => {
- expect(mapDispatchToProps.setShow).toEqual(actions.app.setShowImportSuccessToast);
- });
- });
-});
diff --git a/src/components/GradesView/InterventionsReport.jsx b/src/components/GradesView/InterventionsReport.jsx
deleted file mode 100644
index 5a0402f..0000000
--- a/src/components/GradesView/InterventionsReport.jsx
+++ /dev/null
@@ -1,72 +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 { FormattedMessage } from '@edx/frontend-platform/i18n';
-
-import actions from 'data/actions';
-import selectors from 'data/selectors';
-
-import NetworkButton from 'components/NetworkButton';
-import messages from './InterventionsReport.messages';
-
-/**
- *
- * Provides download buttons for Bulk Management and Intervention reports, only if
- * showBulkManagement is set in redus.
- */
-export class InterventionsReport extends React.Component {
- constructor(props) {
- super(props);
- this.handleClick = this.handleClick.bind(this);
- }
-
- handleClick() {
- this.props.downloadInterventionReport();
- window.location.assign(this.props.interventionExportUrl);
- }
-
- render() {
- return this.props.showBulkManagement && (
-
- );
- }
-}
-
-InterventionsReport.defaultProps = {
- showBulkManagement: false,
-};
-
-InterventionsReport.propTypes = {
- // redux
- downloadInterventionReport: PropTypes.func.isRequired,
- interventionExportUrl: PropTypes.string.isRequired,
- showBulkManagement: PropTypes.bool,
-};
-
-export const mapStateToProps = (state) => ({
- interventionExportUrl: selectors.root.interventionExportUrl(state),
- showBulkManagement: selectors.root.showBulkManagement(state),
-});
-
-export const mapDispatchToProps = {
- downloadInterventionReport: actions.grades.downloadReport.intervention,
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(InterventionsReport);
diff --git a/src/components/GradesView/InterventionsReport.messages.js b/src/components/GradesView/InterventionsReport.messages.js
deleted file mode 100644
index 373805f..0000000
--- a/src/components/GradesView/InterventionsReport.messages.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { defineMessages } from '@edx/frontend-platform/i18n';
-
-const messages = defineMessages({
- title: {
- id: 'gradebook.GradesView.InterventionsReport.title',
- defaultMessage: 'Interventions Report',
- description: 'The title for the Intervention report subsection',
- },
- description: {
- id: 'gradebook.GradesView.InterventionsReport.description',
- defaultMessage: 'Need to find students who may be falling behind? Download the interventions report to obtain engagement metrics such as section attempts and visits.',
- description: 'The description for the Intervention report subsection',
- },
- downloadBtn: {
- id: 'gradebook.GradesView.InterventionsReport.downloadBtn',
- defaultMessage: 'Download Interventions',
- description: 'The labeled button to download the Intervention report from the Grades View',
- },
-});
-
-export default messages;
diff --git a/src/components/GradesView/InterventionsReport.test.jsx b/src/components/GradesView/InterventionsReport.test.jsx
deleted file mode 100644
index 3dfe2b5..0000000
--- a/src/components/GradesView/InterventionsReport.test.jsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-
-import selectors from 'data/selectors';
-import actions from 'data/actions';
-
-import {
- InterventionsReport,
- mapStateToProps,
- mapDispatchToProps,
-} from './InterventionsReport';
-
-jest.mock('@edx/paragon', () => ({
- Toast: () => 'Toast',
-}));
-jest.mock('components/NetworkButton', () => 'NetworkButton');
-jest.mock('data/selectors', () => ({
- __esModule: true,
- default: {
- root: {
- interventionExportUrl: (state) => ({ interventionExportUrl: state }),
- showBulkManagement: (state) => ({ showBulkManagement: state }),
- },
- },
-}));
-jest.mock('data/actions', () => ({
- __esModule: true,
- default: {
- grades: {
- downloadReport: { intervention: jest.fn() },
- },
- },
-}));
-
-describe('InterventionsReport component', () => {
- let el;
- let props = {
- interventionExportUrl: 'url.for.exporting.interventions',
- showBulkManagement: true,
- };
- let location;
- beforeAll(() => {
- location = window.location;
- });
- beforeEach(() => {
- delete window.location;
- window.location = Object.defineProperties(
- {},
- {
- ...Object.getOwnPropertyDescriptors(location),
- assign: { configurable: true, value: jest.fn() },
- },
- );
- props = {
- ...props,
- downloadInterventionReport: jest.fn(),
- };
- });
- afterAll(() => {
- window.location = location;
- });
- describe('snapshots', () => {
- beforeEach(() => {
- el = shallow();
- });
- test('snapshot', () => {
- el.instance().handleClick = jest.fn().mockName('handleClick');
- expect(el.instance().render()).toMatchSnapshot();
- });
- test('returns empty if props.showBulkManagement is false', () => {
- el.setProps({ showBulkManagement: false });
- expect(el.instance().render()).toEqual(false);
- });
- });
- describe('behavior', () => {
- beforeEach(() => {
- el = shallow();
- });
- describe('handleClick', () => {
- it('calls props.downloadInterventionReport and navigates to props.interventionExportUrl', () => {
- el.instance().handleClick();
- expect(props.downloadInterventionReport).toHaveBeenCalled();
- });
- });
- });
- describe('mapStateToProps', () => {
- const testState = { somewhere: 'over', the: 'rainbow' };
- const mapped = mapStateToProps(testState);
- test('interventionExportUrl from root interventionExportUrl selector', () => {
- expect(mapped.interventionExportUrl).toEqual(
- selectors.root.interventionExportUrl(testState),
- );
- });
- test('showBulkManagement from root showBulkManagement selector', () => {
- expect(mapped.showBulkManagement).toEqual(
- selectors.root.showBulkManagement(testState),
- );
- });
- });
- describe('mapDispatchToProps', () => {
- test('downloadInterventionReport from actions.grades.downloadReport.intervention', () => {
- expect(mapDispatchToProps.downloadInterventionReport).toEqual(
- actions.grades.downloadReport.intervention,
- );
- });
- });
-});
diff --git a/src/components/GradesView/ScoreViewInput.jsx b/src/components/GradesView/ScoreViewInput.jsx
deleted file mode 100644
index 6759573..0000000
--- a/src/components/GradesView/ScoreViewInput.jsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-
-import { FormControl, FormGroup, FormLabel } from '@edx/paragon';
-import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
-
-import actions from 'data/actions';
-import selectors from 'data/selectors';
-import messages from './ScoreViewInput.messages';
-
-/**
- *
- * redux-connected select control for grade format (percent vs absolute)
- */
-export const ScoreViewInput = ({ format, intl, toggleFormat }) => (
-
- :
-
-
-
-
-
-);
-ScoreViewInput.defaultProps = {
- format: 'percent',
-};
-ScoreViewInput.propTypes = {
- // injected
- intl: intlShape.isRequired,
- // redux
- format: PropTypes.string,
- toggleFormat: PropTypes.func.isRequired,
-};
-
-export const mapStateToProps = (state) => ({
- format: selectors.grades.gradeFormat(state),
-});
-
-export const mapDispatchToProps = {
- toggleFormat: actions.grades.toggleGradeFormat,
-};
-
-export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ScoreViewInput));
diff --git a/src/components/GradesView/ScoreViewInput.messages.js b/src/components/GradesView/ScoreViewInput.messages.js
deleted file mode 100644
index ed1dca5..0000000
--- a/src/components/GradesView/ScoreViewInput.messages.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { defineMessages } from '@edx/frontend-platform/i18n';
-
-const messages = defineMessages({
- scoreView: {
- id: 'gradebook.GradesView.scoreViewLabel',
- defaultMessage: 'Score View',
- description: 'The label for the dropdown list that allows a user to select the Score format',
- },
- absolute: {
- id: 'gradebook.GradesView.absoluteOption',
- defaultMessage: 'Absolute',
- description: 'A label within the Score Format dropdown list for the Absolute Grade Score option',
- },
- percent: {
- id: 'gradebook.GradesView.percentOption',
- defaultMessage: 'Percent',
- description: 'A label within the Score Format dropdown list for the Percent Grade Score option',
- },
-});
-
-export default messages;
diff --git a/src/components/GradesView/ScoreViewInput.test.jsx b/src/components/GradesView/ScoreViewInput.test.jsx
deleted file mode 100644
index 1c73418..0000000
--- a/src/components/GradesView/ScoreViewInput.test.jsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-
-import actions from 'data/actions';
-import selectors from 'data/selectors';
-
-import {
- ScoreViewInput,
- mapDispatchToProps,
- mapStateToProps,
-} from './ScoreViewInput';
-
-jest.mock('@edx/paragon', () => ({
- FormControl: () => 'FormControl',
- FormGroup: () => 'FormGroup',
- FormLabel: () => 'FormLabel',
-}));
-
-jest.mock('data/actions', () => ({
- __esModule: true,
- default: {
- grades: { toggleGradeFormat: jest.fn() },
- },
-}));
-jest.mock('data/selectors', () => ({
- __esModule: true,
- default: {
- grades: { gradeFormat: (state) => ({ gradeFormat: state }) },
- },
-}));
-
-describe('ScoreViewInput', () => {
- describe('component', () => {
- const props = { format: 'percent' };
- let el;
- beforeEach(() => {
- props.toggleFormat = jest.fn();
- props.intl = { formatMessage: (msg) => msg.defaultMessage };
- el = shallow();
- });
- const assertions = [
- 'select box with percent and absolute options',
- 'onClick from props.toggleFormat',
- ];
- test(`snapshot - ${assertions.join(' and ')}`, () => {
- expect(el).toMatchSnapshot();
- });
- });
- describe('mapStateToProps', () => {
- test('format from grades.gradeFormat', () => {
- const testState = { some: 'state' };
- expect(mapStateToProps(testState).format).toEqual(selectors.grades.gradeFormat(testState));
- });
- });
- describe('mapDispatchToProps', () => {
- test('toggleFormat from actions.grades.toggleGradeFormat', () => {
- expect(mapDispatchToProps.toggleFormat).toEqual(actions.grades.toggleGradeFormat);
- });
- });
-});
diff --git a/src/components/GradesView/SearchControls.jsx b/src/components/GradesView/SearchControls.jsx
deleted file mode 100644
index 08124fe..0000000
--- a/src/components/GradesView/SearchControls.jsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-
-import { SearchField } from '@edx/paragon';
-import { FormattedMessage } from '@edx/frontend-platform/i18n';
-
-import actions from 'data/actions';
-import selectors from 'data/selectors';
-import thunkActions from 'data/thunkActions';
-
-import messages from './SearchControls.messages';
-
-/**
- * Controls for filtering the GradebookTable. Contains the "Edit Filters" button for opening the filter drawer
- * as well as the search box for searching by username/email.
- */
-export class SearchControls extends React.Component {
- constructor(props) {
- super(props);
-
- this.onBlur = this.onBlur.bind(this);
- this.onClear = this.onClear.bind(this);
- this.onSubmit = this.onSubmit.bind(this);
- }
-
- onBlur(e) {
- this.props.setSearchValue(e.target.value);
- }
-
- onClear() {
- this.props.setSearchValue('');
- this.props.fetchGrades();
- }
-
- onSubmit(searchValue) {
- this.props.setSearchValue(searchValue);
- this.props.fetchGrades();
- }
-
- render() {
- return (
-
- }
- onBlur={this.onBlur}
- onClear={this.onClear}
- value={this.props.searchValue}
- />
-
-
-
-
- );
- }
-}
-
-SearchControls.propTypes = {
- // From Redux
- fetchGrades: PropTypes.func.isRequired,
- searchValue: PropTypes.string.isRequired,
- setSearchValue: PropTypes.func.isRequired,
-};
-
-export const mapStateToProps = (state) => ({
- searchValue: selectors.app.searchValue(state),
-});
-
-export const mapDispatchToProps = {
- fetchGrades: thunkActions.grades.fetchGrades,
- setSearchValue: actions.app.setSearchValue,
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(SearchControls);
diff --git a/src/components/GradesView/SearchControls.messages.js b/src/components/GradesView/SearchControls.messages.js
deleted file mode 100644
index 79b117f..0000000
--- a/src/components/GradesView/SearchControls.messages.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import { defineMessages } from '@edx/frontend-platform/i18n';
-
-const messages = defineMessages({
- label: {
- id: 'gradebook.GradesView.search.label',
- defaultMessage: 'Search for a learner',
- description: 'Text prompting a user to use this functionality to search for a learner',
- },
- hint: {
- id: 'gradebook.GradesView.search.hint',
- defaultMessage: 'Search by username, email, or student key',
- description: 'A hint explaining the ways a user can search',
- },
-});
-
-export default messages;
diff --git a/src/components/GradesView/SearchControls.test.jsx b/src/components/GradesView/SearchControls.test.jsx
deleted file mode 100644
index b181d91..0000000
--- a/src/components/GradesView/SearchControls.test.jsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-
-import selectors from 'data/selectors';
-import actions from 'data/actions';
-import thunkActions from 'data/thunkActions';
-import {
- mapDispatchToProps,
- mapStateToProps,
- SearchControls,
-} from './SearchControls';
-
-jest.mock('@edx/paragon', () => ({
- Icon: 'Icon',
- Button: 'Button',
- SearchField: 'SearchField',
-}));
-jest.mock('data/selectors', () => ({
- __esModule: true,
- default: {
- app: {
- searchValue: jest.fn((state) => ({ searchValue: state })),
- },
- },
-}));
-jest.mock('data/thunkActions', () => ({
- __esModule: true,
- default: {
- grades: {
- fetchGrades: jest.fn().mockName('thunkActions.grades.fetchGrades'),
- },
- app: {
- filterMenu: { toggle: jest.fn().mockName('thunkActions.app.filterMenu') },
- },
- },
-}));
-
-describe('SearchControls', () => {
- let props;
-
- beforeEach(() => {
- jest.resetAllMocks();
- props = {
- searchValue: 'alice',
- setSearchValue: jest.fn(),
- fetchGrades: jest.fn().mockName('fetchGrades'),
- };
- });
-
- const searchControls = (overriddenProps) => {
- props = { ...props, ...overriddenProps };
- return shallow();
- };
-
- describe('Component', () => {
- describe('Snapshots', () => {
- test('basic snapshot', () => {
- const wrapper = searchControls();
- wrapper.instance().onBlur = jest.fn().mockName('onBlur');
- wrapper.instance().onClear = jest.fn().mockName('onClear');
- wrapper.instance().onSubmit = jest.fn().mockName('onSubmit');
- expect(wrapper.instance().render()).toMatchSnapshot();
- });
- });
-
- describe('Behavior', () => {
- describe('onBlur', () => {
- it('saves the search value to Gradebook state but do not fetch grade', () => {
- const wrapper = searchControls();
- const event = {
- target: {
- value: 'bob',
- },
- };
- wrapper.instance().onBlur(event);
- expect(props.setSearchValue).toHaveBeenCalledWith('bob');
- expect(props.fetchGrades).not.toHaveBeenCalled();
- });
- });
-
- describe('onClear', () => {
- it('sets search value to empty string and calls fetchGrades', () => {
- const wrapper = searchControls();
- wrapper.instance().onClear();
- expect(props.setSearchValue).toHaveBeenCalledWith('');
- expect(props.fetchGrades).toHaveBeenCalled();
- });
- });
-
- describe('onSubmit', () => {
- it('sets search value to input and calls fetchGrades', () => {
- const wrapper = searchControls();
-
- wrapper.instance().onSubmit('John');
- expect(props.setSearchValue).toHaveBeenCalledWith('John');
- expect(props.fetchGrades).toHaveBeenCalled();
- });
- });
- });
-
- describe('mapStateToProps', () => {
- const testState = { never: 'gonna', give: 'you up' };
- test('searchValue from app.searchValue', () => {
- expect(
- mapStateToProps(testState).searchValue,
- ).toEqual(selectors.app.searchValue(testState));
- });
- });
- describe('mapDispatchToProps', () => {
- test('fetchGrades from thunkActions.grades.fetchGrades', () => {
- expect(mapDispatchToProps.fetchGrades).toEqual(thunkActions.grades.fetchGrades);
- });
-
- test('setSearchValue from actions.app.setSearchValue', () => {
- expect(mapDispatchToProps.setSearchValue).toEqual(actions.app.setSearchValue);
- });
- });
- });
-});
diff --git a/src/components/GradesView/SpinnerIcon.jsx b/src/components/GradesView/SpinnerIcon.jsx
index 398201c..da044d6 100644
--- a/src/components/GradesView/SpinnerIcon.jsx
+++ b/src/components/GradesView/SpinnerIcon.jsx
@@ -1,31 +1,22 @@
-/* eslint-disable react/sort-comp, react/button-has-type, import/no-named-as-default */
import React from 'react';
-import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
import { Icon } from '@edx/paragon';
-import selectors from 'data/selectors';
+import { selectors } from 'data/redux/hooks';
/**
*
* Simmple redux-connected icon component that shows a spinner overlay only if
* redux state says it should.
*/
-export const SpinnerIcon = ({ show }) => show && (
-
-
-
-);
-SpinnerIcon.defaultProps = {
- show: false,
-};
-SpinnerIcon.propTypes = {
- show: PropTypes.bool,
+export const SpinnerIcon = () => {
+ const show = selectors.root.useShouldShowSpinner();
+ return show && (
+
+
+
+ );
};
+SpinnerIcon.propTypes = {};
-export const mapStateToProps = (state) => ({
- show: selectors.root.shouldShowSpinner(state),
-});
-
-export default connect(mapStateToProps)(SpinnerIcon);
+export default SpinnerIcon;
diff --git a/src/components/GradesView/SpinnerIcon.test.jsx b/src/components/GradesView/SpinnerIcon.test.jsx
index 0ffd088..031cf1b 100644
--- a/src/components/GradesView/SpinnerIcon.test.jsx
+++ b/src/components/GradesView/SpinnerIcon.test.jsx
@@ -1,32 +1,35 @@
import React from 'react';
import { shallow } from 'enzyme';
-import selectors from 'data/selectors';
-import { SpinnerIcon, mapStateToProps } from './SpinnerIcon';
+import { selectors } from 'data/redux/hooks';
+import SpinnerIcon from './SpinnerIcon';
-jest.mock('@edx/paragon', () => ({
- Icon: () => 'Icon',
-}));
-jest.mock('data/selectors', () => ({
- __esModule: true,
- default: {
- root: { shouldShowSpinner: state => ({ shouldShowSpinner: state }) },
+jest.mock('data/redux/hooks', () => ({
+ selectors: {
+ root: { useShouldShowSpinner: jest.fn() },
},
}));
+selectors.root.useShouldShowSpinner.mockReturnValue(true);
+let el;
describe('SpinnerIcon', () => {
- describe('component', () => {
- it('snapshot - does not render if show: false', () => {
- expect(shallow()).toMatchSnapshot();
- });
- test('snapshot - displays spinner overlay with spinner icon', () => {
- expect(shallow()).toMatchSnapshot();
+ beforeEach(() => {
+ jest.clearAllMocks();
+ el = shallow();
+ });
+ describe('behavior', () => {
+ it('initializes redux hook', () => {
+ expect(selectors.root.useShouldShowSpinner).toHaveBeenCalled();
});
});
- describe('mapStateToProps', () => {
- const testState = { a: 'nice', day: 'for', some: 'sun' };
- test('show from root.shouldShowSpinner', () => {
- expect(mapStateToProps(testState).show).toEqual(selectors.root.shouldShowSpinner(testState));
+ describe('component', () => {
+ it('does not render if show: false', () => {
+ selectors.root.useShouldShowSpinner.mockReturnValueOnce(false);
+ el = shallow();
+ expect(el.isEmptyRender()).toEqual(true);
+ });
+ test('snapshot - displays spinner overlay with spinner icon', () => {
+ expect(el).toMatchSnapshot();
});
});
});
diff --git a/src/components/GradesView/StatusAlerts.jsx b/src/components/GradesView/StatusAlerts.jsx
deleted file mode 100644
index 74f60fb..0000000
--- a/src/components/GradesView/StatusAlerts.jsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-
-import { Alert } from '@edx/paragon';
-import { FormattedMessage } from '@edx/frontend-platform/i18n';
-
-import selectors from 'data/selectors';
-import actions from 'data/actions';
-import messages from './StatusAlerts.messages';
-
-export class StatusAlerts extends React.Component {
- get isCourseGradeFilterAlertOpen() {
- return (
- !this.props.limitValidity.isMinValid
- || !this.props.limitValidity.isMaxValid
- );
- }
-
- get minValidityMessage() {
- return (this.props.limitValidity.isMinValid)
- ? ''
- : ;
- }
-
- get maxValidityMessage() {
- return (this.props.limitValidity.isMaxValid)
- ? ''
- : ;
- }
-
- get courseGradeFilterAlertDialogText() {
- return (
- <>
- {this.minValidityMessage}{this.maxValidityMessage}
- >
- );
- }
-
- render() {
- return (
- <>
-
-
-
-
- {this.courseGradeFilterAlertDialogText}
-
- >
- );
- }
-}
-
-StatusAlerts.defaultProps = {
-};
-
-StatusAlerts.propTypes = {
- // redux
- handleCloseSuccessBanner: PropTypes.func.isRequired,
- limitValidity: PropTypes.shape({
- isMaxValid: PropTypes.bool,
- isMinValid: PropTypes.bool,
- }).isRequired,
- showSuccessBanner: PropTypes.bool.isRequired,
-};
-
-export const mapStateToProps = (state) => ({
- limitValidity: selectors.app.courseGradeFilterValidity(state),
- showSuccessBanner: selectors.grades.showSuccess(state),
-});
-
-export const mapDispatchToProps = {
- handleCloseSuccessBanner: actions.grades.banner.close,
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(StatusAlerts);
diff --git a/src/components/GradesView/StatusAlerts.test.jsx b/src/components/GradesView/StatusAlerts.test.jsx
deleted file mode 100644
index ba2d799..0000000
--- a/src/components/GradesView/StatusAlerts.test.jsx
+++ /dev/null
@@ -1,123 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-
-import { FormattedMessage } from '@edx/frontend-platform/i18n';
-
-import actions from 'data/actions';
-import selectors from 'data/selectors';
-import messages from './StatusAlerts.messages';
-import {
- StatusAlerts,
- mapDispatchToProps,
- mapStateToProps,
-} from './StatusAlerts';
-
-jest.mock('@edx/paragon', () => ({
- Alert: 'Alert',
-}));
-jest.mock('data/selectors', () => ({
- __esModule: true,
- default: {
- app: {
- courseGradeFilterValidity: (state) => ({ courseGradeFilterValidity: state }),
- },
- grades: {
- showSuccess: (state) => ({ showSuccess: state }),
- },
- },
-}));
-
-describe('StatusAlerts', () => {
- let props = {
- showSuccessBanner: true,
- limitValidity: {
- isMaxValid: true,
- isMinValid: true,
- },
- };
-
- beforeEach(() => {
- props = {
- ...props,
- handleCloseSuccessBanner: jest.fn().mockName('handleCloseSuccessBanner'),
- };
- });
-
- describe('snapshots', () => {
- let el;
- it('basic snapshot', () => {
- el = shallow();
- const courseGradeFilterAlertDialogText = 'the quiCk brown does somEthing or other';
- jest.spyOn(
- el.instance(),
- 'courseGradeFilterAlertDialogText',
- 'get',
- ).mockReturnValue(courseGradeFilterAlertDialogText);
- expect(el.instance().render()).toMatchSnapshot();
- });
- });
-
- describe('behavior', () => {
- it.each([
- [false, false],
- [false, true],
- [true, false],
- [true, true],
- ])('min + max course grade validity', (isMinValid, isMaxValid) => {
- props = {
- ...props,
- limitValidity: {
- isMinValid,
- isMaxValid,
- },
- };
- const el = shallow();
- expect(
- el.instance().isCourseGradeFilterAlertOpen,
- ).toEqual(
- !isMinValid || !isMaxValid,
- );
- if (!isMaxValid) {
- if (!isMinValid) {
- expect(el.instance().courseGradeFilterAlertDialogText).toEqual(
- <>
-
-
- >,
- );
- } else {
- expect(
- el.instance().courseGradeFilterAlertDialogText,
- // eslint-disable-next-line react/jsx-curly-brace-presence
- ).toEqual(<>{''}>);
- }
- } else if (!isMinValid) {
- expect(
- el.instance().courseGradeFilterAlertDialogText,
- // eslint-disable-next-line react/jsx-curly-brace-presence
- ).toEqual(<>{''}>);
- }
- });
- });
-
- describe('mapStateToProps', () => {
- const testState = { A: 'pple', B: 'anana', C: 'ucumber' };
- let mapped;
- beforeEach(() => {
- mapped = mapStateToProps(testState);
- });
- test('limitValidity from app.courseGradeFitlerValidity', () => {
- expect(mapped.limitValidity).toEqual(selectors.app.courseGradeFilterValidity(testState));
- });
- test('showSuccessBanner from grades.showSuccess', () => {
- expect(mapped.showSuccessBanner).toEqual(selectors.grades.showSuccess(testState));
- });
- });
- describe('mapDispatchToProps', () => {
- test('handleCloseSuccessBanner from actions.grades.banner.close', () => {
- expect(
- mapDispatchToProps.handleCloseSuccessBanner,
- ).toEqual(actions.grades.banner.close);
- });
- });
-});
diff --git a/src/components/GradesView/StatusAlerts/__snapshots__/index.test.jsx.snap b/src/components/GradesView/StatusAlerts/__snapshots__/index.test.jsx.snap
new file mode 100644
index 0000000..a842973
--- /dev/null
+++ b/src/components/GradesView/StatusAlerts/__snapshots__/index.test.jsx.snap
@@ -0,0 +1,20 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`StatusAlerts component render snapshot 1`] = `
+
+
+ hooks.success-banner-text
+
+
+ hooks.grade-filter-text
+
+
+`;
diff --git a/src/components/GradesView/StatusAlerts/hooks.js b/src/components/GradesView/StatusAlerts/hooks.js
new file mode 100644
index 0000000..efcd8a5
--- /dev/null
+++ b/src/components/GradesView/StatusAlerts/hooks.js
@@ -0,0 +1,32 @@
+import { useIntl } from '@edx/frontend-platform/i18n';
+
+import { actions, selectors } from 'data/redux/hooks';
+import messages from './messages';
+
+export const useStatusAlertsData = () => {
+ const { formatMessage } = useIntl();
+
+ const limitValidity = selectors.app.useCourseGradeFilterValidity();
+ const showSuccessBanner = selectors.grades.useShowSuccess();
+ const handleCloseSuccessBanner = actions.grades.useCloseBanner();
+
+ const isCourseGradeFilterAlertOpen = !limitValidity.isMinValid || !limitValidity.isMaxValid;
+
+ const validityMessages = {
+ min: limitValidity.isMinValid ? '' : formatMessage(messages.minGradeInvalid),
+ max: limitValidity.isMaxValid ? '' : formatMessage(messages.maxGradeInvalid),
+ };
+
+ return {
+ successBanner: {
+ onClose: handleCloseSuccessBanner,
+ show: showSuccessBanner,
+ text: formatMessage(messages.editSuccessAlert),
+ },
+ gradeFilter: {
+ show: isCourseGradeFilterAlertOpen,
+ text: `${validityMessages.min}${validityMessages.max}`,
+ },
+ };
+};
+export default useStatusAlertsData;
diff --git a/src/components/GradesView/StatusAlerts/hooks.test.js b/src/components/GradesView/StatusAlerts/hooks.test.js
new file mode 100644
index 0000000..4b0e11f
--- /dev/null
+++ b/src/components/GradesView/StatusAlerts/hooks.test.js
@@ -0,0 +1,110 @@
+import { useIntl } from '@edx/frontend-platform/i18n';
+
+import { formatMessage } from 'testUtils';
+import { actions, selectors } from 'data/redux/hooks';
+
+import useStatusAlertsData from './hooks';
+import messages from './messages';
+
+jest.mock('data/redux/hooks', () => ({
+ actions: {
+ grades: { useCloseBanner: jest.fn() },
+ },
+ selectors: {
+ app: { useCourseGradeFilterValidity: jest.fn() },
+ grades: { useShowSuccess: jest.fn() },
+ },
+}));
+
+const validity = {
+ isMinValid: true,
+ isMaxValid: true,
+};
+selectors.app.useCourseGradeFilterValidity.mockReturnValue(validity);
+const showSuccess = 'test-show-success';
+selectors.grades.useShowSuccess.mockReturnValue(showSuccess);
+const closeBanner = jest.fn().mockName('hooks.closeBanner');
+actions.grades.useCloseBanner.mockReturnValue(closeBanner);
+
+let out;
+describe('useStatusAlertsData', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ out = useStatusAlertsData();
+ });
+ describe('behavior', () => {
+ it('initializes intl hook', () => {
+ expect(useIntl).toHaveBeenCalled();
+ });
+ it('initializes redux hooks', () => {
+ expect(actions.grades.useCloseBanner).toHaveBeenCalled();
+ expect(selectors.app.useCourseGradeFilterValidity).toHaveBeenCalled();
+ expect(selectors.grades.useShowSuccess).toHaveBeenCalled();
+ });
+ });
+ describe('output', () => {
+ describe('successBanner', () => {
+ test('onClose and show from redux', () => {
+ expect(out.successBanner.onClose).toEqual(closeBanner);
+ expect(out.successBanner.show).toEqual(showSuccess);
+ });
+ test('message', () => {
+ expect(out.successBanner.text).toEqual(formatMessage(messages.editSuccessAlert));
+ });
+ });
+ describe('gradeFilter', () => {
+ describe('both filters are valid', () => {
+ test('do not show', () => {
+ expect(out.gradeFilter.show).toEqual(false);
+ });
+ });
+ describe('min filter is invalid', () => {
+ beforeEach(() => {
+ selectors.app.useCourseGradeFilterValidity.mockReturnValue({
+ isMinValid: false,
+ isMaxValid: true,
+ });
+ out = useStatusAlertsData();
+ });
+ test('show grade filter banner', () => {
+ expect(out.gradeFilter.show).toEqual(true);
+ });
+ test('filter message', () => {
+ expect(out.gradeFilter.text).toEqual(formatMessage(messages.minGradeInvalid));
+ });
+ });
+ describe('max filter is invalid', () => {
+ beforeEach(() => {
+ selectors.app.useCourseGradeFilterValidity.mockReturnValue({
+ isMinValid: true,
+ isMaxValid: false,
+ });
+ out = useStatusAlertsData();
+ });
+ test('show grade filter banner', () => {
+ expect(out.gradeFilter.show).toEqual(true);
+ });
+ test('filter message', () => {
+ expect(out.gradeFilter.text).toEqual(formatMessage(messages.maxGradeInvalid));
+ });
+ });
+ describe('both filters are invalid', () => {
+ beforeEach(() => {
+ selectors.app.useCourseGradeFilterValidity.mockReturnValue({
+ isMinValid: false,
+ isMaxValid: false,
+ });
+ out = useStatusAlertsData();
+ });
+ test('show grade filter banner', () => {
+ expect(out.gradeFilter.show).toEqual(true);
+ });
+ test('filter message', () => {
+ expect(out.gradeFilter.text).toEqual(
+ `${formatMessage(messages.minGradeInvalid)}${formatMessage(messages.maxGradeInvalid)}`,
+ );
+ });
+ });
+ });
+ });
+});
diff --git a/src/components/GradesView/StatusAlerts/index.jsx b/src/components/GradesView/StatusAlerts/index.jsx
new file mode 100644
index 0000000..f7883ff
--- /dev/null
+++ b/src/components/GradesView/StatusAlerts/index.jsx
@@ -0,0 +1,35 @@
+import React from 'react';
+
+import { Alert } from '@edx/paragon';
+
+import useStatusAlertsData from './hooks';
+
+export const StatusAlerts = () => {
+ const {
+ successBanner,
+ gradeFilter,
+ } = useStatusAlertsData();
+
+ return (
+ <>
+
+ {successBanner.text}
+
+
+ {gradeFilter.text}
+
+ >
+ );
+};
+
+StatusAlerts.propTypes = {};
+
+export default StatusAlerts;
diff --git a/src/components/GradesView/StatusAlerts/index.test.jsx b/src/components/GradesView/StatusAlerts/index.test.jsx
new file mode 100644
index 0000000..408044d
--- /dev/null
+++ b/src/components/GradesView/StatusAlerts/index.test.jsx
@@ -0,0 +1,53 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import { Alert } from '@edx/paragon';
+
+import useStatusAlertsData from './hooks';
+import StatusAlerts from '.';
+
+jest.mock('./hooks', () => jest.fn());
+
+const hookProps = {
+ successBanner: {
+ onClose: jest.fn().mockName('hooks.successBanner.onClose'),
+ show: 'hooks.show-success-banner',
+ text: 'hooks.success-banner-text',
+ },
+ gradeFilter: {
+ show: 'hooks.show-grade-filter',
+ text: 'hooks.grade-filter-text',
+ },
+};
+useStatusAlertsData.mockReturnValue(hookProps);
+
+let el;
+describe('StatusAlerts component', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ el = shallow();
+ });
+ describe('behavior', () => {
+ it('initializes component hooks', () => {
+ expect(useStatusAlertsData).toHaveBeenCalled();
+ });
+ });
+ describe('render', () => {
+ test('snapshot', () => {
+ expect(el).toMatchSnapshot();
+ });
+ test('success banner', () => {
+ const alert = el.find(Alert).at(0);
+ const props = alert.props();
+ expect(props.onClose).toEqual(hookProps.successBanner.onClose);
+ expect(props.show).toEqual(hookProps.successBanner.show);
+ expect(alert.text()).toEqual(hookProps.successBanner.text);
+ });
+ test('grade filter banner', () => {
+ const alert = el.find(Alert).at(1);
+ const props = alert.props();
+ expect(props.show).toEqual(hookProps.gradeFilter.show);
+ expect(alert.text()).toEqual(hookProps.gradeFilter.text);
+ });
+ });
+});
diff --git a/src/components/GradesView/StatusAlerts.messages.js b/src/components/GradesView/StatusAlerts/messages.js
similarity index 100%
rename from src/components/GradesView/StatusAlerts.messages.js
rename to src/components/GradesView/StatusAlerts/messages.js
diff --git a/src/components/GradesView/UsersLabel.jsx b/src/components/GradesView/UsersLabel.jsx
deleted file mode 100644
index ac6df0f..0000000
--- a/src/components/GradesView/UsersLabel.jsx
+++ /dev/null
@@ -1,44 +0,0 @@
-/* eslint-disable react/sort-comp, react/button-has-type, import/no-named-as-default */
-import React from 'react';
-import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-
-import { FormattedMessage } from '@edx/frontend-platform/i18n';
-
-import selectors from 'data/selectors';
-
-/**
- *
- * Simple label component displaying the filtered and total users shown
- */
-export const UsersLabel = ({
- filteredUsersCount,
- totalUsersCount,
-}) => {
- if (!totalUsersCount) {
- return null;
- }
- const bold = (val) => ({val});
- return (
-
- );
-};
-UsersLabel.propTypes = {
- filteredUsersCount: PropTypes.number.isRequired,
- totalUsersCount: PropTypes.number.isRequired,
-};
-
-export const mapStateToProps = (state) => ({
- totalUsersCount: selectors.grades.totalUsersCount(state),
- filteredUsersCount: selectors.grades.filteredUsersCount(state),
-});
-
-export default connect(mapStateToProps)(UsersLabel);
diff --git a/src/components/GradesView/UsersLabel.test.jsx b/src/components/GradesView/UsersLabel.test.jsx
deleted file mode 100644
index 43a4335..0000000
--- a/src/components/GradesView/UsersLabel.test.jsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-
-import selectors from 'data/selectors';
-import { UsersLabel, mapStateToProps } from './UsersLabel';
-
-jest.mock('@edx/paragon', () => ({
- Icon: () => 'Icon',
-}));
-jest.mock('data/selectors', () => ({
- __esModule: true,
- default: {
- grades: {
- filteredUsersCount: state => ({ filteredUsersCount: state }),
- totalUsersCount: state => ({ totalUsersCount: state }),
- },
- },
-}));
-
-describe('UsersLabel', () => {
- describe('component', () => {
- const props = {
- filteredUsersCount: 23,
- totalUsersCount: 140,
- };
- it('does not render if totalUsersCount is falsey', () => {
- expect(shallow()).toEqual({});
- });
- test('snapshot - displays label with number of filtered users out of total', () => {
- expect(shallow()).toMatchSnapshot();
- });
- });
- describe('mapStateToProps', () => {
- const testState = { a: 'nice', day: 'for', some: 'rain' };
- let mapped;
- beforeEach(() => {
- mapped = mapStateToProps(testState);
- });
- test('filteredUsersCount from grades.filteredUsersCount', () => {
- expect(mapped.filteredUsersCount).toEqual(selectors.grades.filteredUsersCount(testState));
- });
- test('totalUsersCount from grades.totalUsersCount', () => {
- expect(mapped.totalUsersCount).toEqual(selectors.grades.totalUsersCount(testState));
- });
- });
-});
diff --git a/src/components/GradesView/__snapshots__/FilterMenuToggle.test.jsx.snap b/src/components/GradesView/__snapshots__/FilterMenuToggle.test.jsx.snap
deleted file mode 100644
index 9b66b38..0000000
--- a/src/components/GradesView/__snapshots__/FilterMenuToggle.test.jsx.snap
+++ /dev/null
@@ -1,19 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`FilterMenuToggle component snapshots basic snapshot 1`] = `
-
-`;
diff --git a/src/components/GradesView/__snapshots__/FilteredUsersLabel.test.jsx.snap b/src/components/GradesView/__snapshots__/FilteredUsersLabel.test.jsx.snap
deleted file mode 100644
index 2086de8..0000000
--- a/src/components/GradesView/__snapshots__/FilteredUsersLabel.test.jsx.snap
+++ /dev/null
@@ -1,23 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`FilteredUsersLabel component snapshot - displays label with number of filtered users out of total 1`] = `
-
- 23
- ,
- "totalUsers":
- 140
- ,
- }
- }
-/>
-`;
diff --git a/src/components/GradesView/__snapshots__/ImportSuccessToast.test.jsx.snap b/src/components/GradesView/__snapshots__/ImportSuccessToast.test.jsx.snap
deleted file mode 100644
index b42d994..0000000
--- a/src/components/GradesView/__snapshots__/ImportSuccessToast.test.jsx.snap
+++ /dev/null
@@ -1,16 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ImportSuccessToast component snapshots snapshot 1`] = `
-
- Import Successful! Grades will be updated momentarily.
-
-`;
diff --git a/src/components/GradesView/__snapshots__/InterventionsReport.test.jsx.snap b/src/components/GradesView/__snapshots__/InterventionsReport.test.jsx.snap
deleted file mode 100644
index 44210b1..0000000
--- a/src/components/GradesView/__snapshots__/InterventionsReport.test.jsx.snap
+++ /dev/null
@@ -1,38 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`InterventionsReport component snapshots snapshot 1`] = `
-
-`;
diff --git a/src/components/GradesView/__snapshots__/ScoreViewInput.test.jsx.snap b/src/components/GradesView/__snapshots__/ScoreViewInput.test.jsx.snap
deleted file mode 100644
index 44b805a..0000000
--- a/src/components/GradesView/__snapshots__/ScoreViewInput.test.jsx.snap
+++ /dev/null
@@ -1,32 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ScoreViewInput component snapshot - select box with percent and absolute options and onClick from props.toggleFormat 1`] = `
-
-
-
- :
-
-
-
-
-
-
-`;
diff --git a/src/components/GradesView/__snapshots__/SearchControls.test.jsx.snap b/src/components/GradesView/__snapshots__/SearchControls.test.jsx.snap
deleted file mode 100644
index 43e3bb7..0000000
--- a/src/components/GradesView/__snapshots__/SearchControls.test.jsx.snap
+++ /dev/null
@@ -1,28 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`SearchControls Component Snapshots basic snapshot 1`] = `
-
-
- }
- onBlur={[MockFunction onBlur]}
- onClear={[MockFunction onClear]}
- onSubmit={[MockFunction onSubmit]}
- value="alice"
- />
-
-
-
-
-`;
diff --git a/src/components/GradesView/__snapshots__/SpinnerIcon.test.jsx.snap b/src/components/GradesView/__snapshots__/SpinnerIcon.test.jsx.snap
index 6a99def..c530fb3 100644
--- a/src/components/GradesView/__snapshots__/SpinnerIcon.test.jsx.snap
+++ b/src/components/GradesView/__snapshots__/SpinnerIcon.test.jsx.snap
@@ -9,5 +9,3 @@ exports[`SpinnerIcon component snapshot - displays spinner overlay with spinner
/>
`;
-
-exports[`SpinnerIcon component snapshot - does not render if show: false 1`] = `""`;
diff --git a/src/components/GradesView/__snapshots__/StatusAlerts.test.jsx.snap b/src/components/GradesView/__snapshots__/StatusAlerts.test.jsx.snap
deleted file mode 100644
index 7edc860..0000000
--- a/src/components/GradesView/__snapshots__/StatusAlerts.test.jsx.snap
+++ /dev/null
@@ -1,24 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`StatusAlerts snapshots basic snapshot 1`] = `
-
-
-
-
-
- the quiCk brown does somEthing or other
-
-
-`;
diff --git a/src/components/GradesView/__snapshots__/UsersLabel.test.jsx.snap b/src/components/GradesView/__snapshots__/UsersLabel.test.jsx.snap
deleted file mode 100644
index 9263159..0000000
--- a/src/components/GradesView/__snapshots__/UsersLabel.test.jsx.snap
+++ /dev/null
@@ -1,23 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`UsersLabel component snapshot - displays label with number of filtered users out of total 1`] = `
-
- 23
- ,
- "totalUsers":
- 140
- ,
- }
- }
-/>
-`;
diff --git a/src/components/GradesView/__snapshots__/index.test.jsx.snap b/src/components/GradesView/__snapshots__/index.test.jsx.snap
new file mode 100644
index 0000000..cc927fa
--- /dev/null
+++ b/src/components/GradesView/__snapshots__/index.test.jsx.snap
@@ -0,0 +1,41 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`GradesView component render snapshot 1`] = `
+
+
+
+
+ filter-step-heading
+
+
+
+
+
+
+
+
+ gradebook-step-heading
+
+
+
+
+
+
+
+
+
+ *
+ test-masters-hint
+
+
+
+
+`;
diff --git a/src/components/GradesView/__snapshots__/test.jsx.snap b/src/components/GradesView/__snapshots__/test.jsx.snap
deleted file mode 100644
index d208f4b..0000000
--- a/src/components/GradesView/__snapshots__/test.jsx.snap
+++ /dev/null
@@ -1,53 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`GradesView Component snapshots basic snapshot 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- *
-
-
-
-
-
-`;
diff --git a/src/components/GradesView/hooks.js b/src/components/GradesView/hooks.js
new file mode 100644
index 0000000..b8f6504
--- /dev/null
+++ b/src/components/GradesView/hooks.js
@@ -0,0 +1,30 @@
+import { useIntl } from '@edx/frontend-platform/i18n';
+
+import { actions, thunkActions } from 'data/redux/hooks';
+import messages from './messages';
+
+export const useGradesViewData = ({ updateQueryParams }) => {
+ const { formatMessage } = useIntl();
+ const fetchGrades = thunkActions.grades.useFetchGrades();
+ const resetFilters = actions.filters.useResetFilters();
+
+ const handleFilterBadgeClose = (filterNames) => () => {
+ resetFilters(filterNames);
+ updateQueryParams(filterNames.reduce(
+ (obj, filterName) => ({ ...obj, [filterName]: false }),
+ {},
+ ));
+ fetchGrades();
+ };
+
+ return {
+ stepHeadings: {
+ filter: formatMessage(messages.filterStepHeading),
+ gradebook: formatMessage(messages.gradebookStepHeading),
+ },
+ handleFilterBadgeClose,
+ mastersHint: formatMessage(messages.mastersHint),
+ };
+};
+
+export default useGradesViewData;
diff --git a/src/components/GradesView/hooks.test.js b/src/components/GradesView/hooks.test.js
new file mode 100644
index 0000000..7e444e0
--- /dev/null
+++ b/src/components/GradesView/hooks.test.js
@@ -0,0 +1,62 @@
+import { useIntl } from '@edx/frontend-platform/i18n';
+
+import { formatMessage } from 'testUtils';
+import { actions, thunkActions } from 'data/redux/hooks';
+
+import useGradesViewData from './hooks';
+import messages from './messages';
+
+jest.mock('data/redux/hooks', () => ({
+ actions: {
+ filters: { useResetFilters: jest.fn() },
+ },
+ thunkActions: {
+ grades: { useFetchGrades: jest.fn() },
+ },
+}));
+
+const fetchGrades = jest.fn();
+thunkActions.grades.useFetchGrades.mockReturnValue(fetchGrades);
+const resetFilters = jest.fn();
+actions.filters.useResetFilters.mockReturnValue(resetFilters);
+
+const updateQueryParams = jest.fn();
+
+let out;
+describe('useGradesViewData', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ out = useGradesViewData({ updateQueryParams });
+ });
+ describe('behavior', () => {
+ it('initializes intl hook', () => {
+ expect(useIntl).toHaveBeenCalled();
+ });
+ it('initializes redux hooks', () => {
+ expect(thunkActions.grades.useFetchGrades).toHaveBeenCalled();
+ expect(actions.filters.useResetFilters).toHaveBeenCalled();
+ });
+ });
+ describe('output', () => {
+ test('stepHeadings', () => {
+ expect(out.stepHeadings.filter).toEqual(formatMessage(messages.filterStepHeading));
+ expect(out.stepHeadings.gradebook).toEqual(formatMessage(messages.gradebookStepHeading));
+ });
+ test('mastersHint', () => {
+ expect(out.mastersHint).toEqual(formatMessage(messages.mastersHint));
+ });
+ describe('handleFilterBadgeClose', () => {
+ it('resets filters locally and in query params, and fetches grades', () => {
+ const filters = ['some', 'filter', 'names'];
+ out.handleFilterBadgeClose(filters)();
+ expect(resetFilters).toHaveBeenCalledWith(filters);
+ expect(updateQueryParams).toHaveBeenCalledWith({
+ some: false,
+ filter: false,
+ names: false,
+ });
+ expect(fetchGrades).toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/src/components/GradesView/index.jsx b/src/components/GradesView/index.jsx
index 47373b1..5c4ddee 100644
--- a/src/components/GradesView/index.jsx
+++ b/src/components/GradesView/index.jsx
@@ -1,12 +1,6 @@
/* eslint-disable react/sort-comp, react/button-has-type, import/no-named-as-default */
import React from 'react';
import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-
-import { FormattedMessage } from '@edx/frontend-platform/i18n';
-
-import actions from 'data/actions';
-import thunkActions from 'data/thunkActions';
import BulkManagementControls from './BulkManagementControls';
import EditModal from './EditModal';
@@ -21,79 +15,55 @@ import ScoreViewInput from './ScoreViewInput';
import SearchControls from './SearchControls';
import SpinnerIcon from './SpinnerIcon';
import StatusAlerts from './StatusAlerts';
-import messages from './messages';
-export class GradesView extends React.Component {
- constructor(props) {
- super(props);
- this.handleFilterBadgeClose = this.handleFilterBadgeClose.bind(this);
- }
+import useGradesViewData from './hooks';
- handleFilterBadgeClose(filterNames) {
- return () => {
- this.props.resetFilters(filterNames);
- this.props.updateQueryParams(filterNames.reduce(
- (obj, filterName) => ({ ...obj, [filterName]: false }),
- {},
- ));
- this.props.fetchGrades();
- };
- }
+export const GradesView = ({ updateQueryParams }) => {
+ const {
+ stepHeadings,
+ handleFilterBadgeClose,
+ mastersHint,
+ } = useGradesViewData({ updateQueryParams });
- render() {
- return (
- <>
-
+ return (
+ <>
+
-
-
-
-
+
+
+ {stepHeadings.filter}
+
-
-
-
-
+
+
+
+
-
-
+
+
-
+ {stepHeadings.gradebook}
-
-
-
-
+
+
+
+
-
+
-
+
-
- *
-
+
+ * {mastersHint}
+
-
- >
- );
- }
-}
-
-GradesView.defaultProps = {};
+
+ >
+ );
+};
GradesView.propTypes = {
updateQueryParams: PropTypes.func.isRequired,
-
- // redux
- fetchGrades: PropTypes.func.isRequired,
- resetFilters: PropTypes.func.isRequired,
};
-export const mapStateToProps = () => ({});
-
-export const mapDispatchToProps = {
- fetchGrades: thunkActions.grades.fetchGrades,
- resetFilters: actions.filters.reset,
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(GradesView);
+export default GradesView;
diff --git a/src/components/GradesView/index.test.jsx b/src/components/GradesView/index.test.jsx
new file mode 100644
index 0000000..2279071
--- /dev/null
+++ b/src/components/GradesView/index.test.jsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import FilterBadges from './FilterBadges';
+
+import useGradesViewData from './hooks';
+import GradesView from '.';
+
+jest.mock('./BulkManagementControls', () => 'BulkManagementControls');
+jest.mock('./EditModal', () => 'EditModal');
+jest.mock('./FilterBadges', () => 'FilterBadges');
+jest.mock('./FilteredUsersLabel', () => 'FilteredUsersLabel');
+jest.mock('./FilterMenuToggle', () => 'FilterMenuToggle');
+jest.mock('./GradebookTable', () => 'GradebookTable');
+jest.mock('./ImportSuccessToast', () => 'ImportSuccessToast');
+jest.mock('./InterventionsReport', () => 'InterventionsReport');
+jest.mock('./PageButtons', () => 'PageButtons');
+jest.mock('./ScoreViewInput', () => 'ScoreViewInput');
+jest.mock('./SearchControls', () => 'SearchControls');
+jest.mock('./SpinnerIcon', () => 'SpinnerIcon');
+jest.mock('./StatusAlerts', () => 'StatusAlerts');
+jest.mock('./hooks', () => jest.fn());
+
+const hookProps = {
+ stepHeadings: {
+ filter: 'filter-step-heading',
+ gradebook: 'gradebook-step-heading',
+ },
+ handleFilterBadgeClose: jest.fn().mockName('hooks.handleFilterBadgeClose'),
+ mastersHint: 'test-masters-hint',
+};
+useGradesViewData.mockReturnValue(hookProps);
+
+const updateQueryParams = jest.fn().mockName('props.updateQueryParams');
+
+let el;
+describe('GradesView component', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ el = shallow();
+ });
+ describe('behavior', () => {
+ it('initializes component hooks', () => {
+ expect(useGradesViewData).toHaveBeenCalled();
+ });
+ });
+ describe('render', () => {
+ test('snapshot', () => {
+ expect(el).toMatchSnapshot();
+ });
+ test('filterBadges load close behavior from hook', () => {
+ expect(el.find(FilterBadges).props().handleClose).toEqual(
+ hookProps.handleFilterBadgeClose,
+ );
+ });
+ });
+});
diff --git a/src/components/GradesView/test.jsx b/src/components/GradesView/test.jsx
deleted file mode 100644
index 847e989..0000000
--- a/src/components/GradesView/test.jsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-
-import actions from 'data/actions';
-import thunkActions from 'data/thunkActions';
-
-import {
- GradesView,
- mapStateToProps,
- mapDispatchToProps,
-} from '.';
-
-jest.mock('data/actions', () => ({
- __esModule: true,
- default: {
- app: { setView: jest.fn() },
- filters: { resetFilters: jest.fn() },
- },
-}));
-jest.mock('data/thunkActions', () => ({
- __esModule: true,
- default: {
- grades: { fetchGrades: jest.fn() },
- },
-}));
-
-jest.mock('./BulkManagementControls', () => 'BulkManagementControls');
-jest.mock('./EditModal', () => 'EditModal');
-jest.mock('./FilterBadges', () => 'FilterBadges');
-jest.mock('./FilteredUsersLabel', () => 'FilteredUsersLabel');
-jest.mock('./FilterMenuToggle', () => 'FilterMenuToggle');
-jest.mock('./GradebookTable', () => 'GradebookTable');
-jest.mock('./ImportSuccessToast', () => 'ImportSuccessToast');
-jest.mock('./InterventionsReport', () => 'InterventionsReport');
-jest.mock('./PageButtons', () => 'PageButtons');
-jest.mock('./ScoreViewInput', () => 'ScoreViewInput');
-jest.mock('./SearchControls', () => 'SearchControls');
-jest.mock('./SpinnerIcon', () => 'SpinnerIcon');
-jest.mock('./StatusAlerts', () => 'StatusAlerts');
-
-describe('GradesView', () => {
- let props;
- beforeEach(() => {
- props = {
- updateQueryParams: jest.fn(),
- fetchGrades: jest.fn(),
- resetFilters: jest.fn(),
- };
- });
-
- describe('Component', () => {
- const filterNames = ['duck', 'Duck', 'Duuuuuck', 'GOOOOSE!'];
- describe('behavior', () => {
- let el;
- beforeEach(() => {
- el = shallow();
- });
- describe('handleFilterBadgeClose', () => {
- beforeEach(() => {
- el.instance().handleFilterBadgeClose(filterNames)();
- });
- it('calls props.resetFilters with the filters', () => {
- expect(props.resetFilters).toHaveBeenCalledWith(filterNames);
- });
- it('calls props.updateQueryParams with a reset-filters obj', () => {
- expect(props.updateQueryParams).toHaveBeenCalledWith({
- [filterNames[0]]: false,
- [filterNames[1]]: false,
- [filterNames[2]]: false,
- [filterNames[3]]: false,
- });
- });
- it('calls fetchGrades', () => {
- expect(props.fetchGrades).toHaveBeenCalledWith();
- });
- });
- });
- describe('snapshots', () => {
- test('basic snapshot', () => {
- const el = shallow();
- el.instance().handleFilterBadgeClose = jest.fn().mockName('this.handleFilterBadgeClose');
- expect(el.instance().render()).toMatchSnapshot();
- });
- });
- });
- test('mapStateToProps is empty', () => {
- expect(mapStateToProps({ some: 'state' })).toEqual({});
- });
- describe('mapDispatchToProps', () => {
- describe('fetchGrades', () => {
- test('from thunkActions.grades.fetchGrades', () => {
- expect(mapDispatchToProps.fetchGrades).toEqual(
- thunkActions.grades.fetchGrades,
- );
- });
- });
- describe('resetFilters', () => {
- test('from actions.filters.reset', () => {
- expect(mapDispatchToProps.resetFilters).toEqual(
- actions.filters.reset,
- );
- });
- });
- });
-});