Compare commits

..

23 Commits

Author SHA1 Message Date
Ben Warzeski
8e19b5774c fix: merge cruft leftover file 2023-05-15 11:32:42 -04:00
Ben Warzeski
f7a4309888 fix: table tests 2023-05-15 11:16:48 -04:00
Omar Al-Ithawi
3e3a73e2bb feat: use atlas in make pull_translations (#325)
Changes
-------
 - Bump frontend-platform to bring `intl-imports.js` script
 - Move all i18n imports into `src/i18n/index.js` so `intl-imports.js` can
   override it with latest translations
 - Add `atlas` into `make pull_translations` when `OPENEDX_ATLAS_PULL`
   environment variable is set.
 - Refactored i18n utils into own file to avoid overwriting them by
   atlas

Refs: [FC-0012 project](https://openedx.atlassian.net/l/cp/XGS0iCcQ) implementing Translation Infrastructure OEP-58.
2023-05-15 10:58:11 -04:00
Ben Warzeski
af8d7182ef fix: date test 2023-05-15 10:57:48 -04:00
Ben Warzeski
d898a9cc2f fix: null renders 2023-05-15 10:57:25 -04:00
Ben Warzeski
f4b839f4d8 fix: lint 2023-05-15 10:57:24 -04:00
Ben Warzeski
f41b237d08 fix: redux transform tests 2023-05-15 10:57:24 -04:00
Ben Warzeski
6dd2fb3dd6 refactor: GradebookFilters component modernization 2023-05-15 10:57:23 -04:00
Ben Warzeski
b173681edb refactor: GradesView component modernization 2023-05-15 10:57:23 -04:00
Ben Warzeski
5fcde3b9e8 refactor: ImportSuccessToast component modernization 2023-05-15 10:57:22 -04:00
Ben Warzeski
35ee68ea9d refactor: SearchControls component modernization 2023-05-15 10:57:22 -04:00
Ben Warzeski
dde8e759b6 refactor: ScoreViewInput component modernization 2023-05-15 10:57:22 -04:00
Ben Warzeski
6b149e9ce0 refactor: PageButtons component modernization 2023-05-15 10:57:21 -04:00
Ben Warzeski
4cf5ba7a07 refactor: FilteredUsersLabel component modernization 2023-05-15 10:57:21 -04:00
Ben Warzeski
7a506324a8 refactor: FilterMenuToggle component modernization 2023-05-15 10:57:20 -04:00
Ben Warzeski
7f54cc4917 refactor: FilterBadges component modernization 2023-05-15 10:57:20 -04:00
Ben Warzeski
134ace9483 refactor: GradebookTable component modernization 2023-05-15 10:57:18 -04:00
Ben Warzeski
0e6f52fca9 refactor: EditModal component updates 2023-05-15 10:50:31 -04:00
Ben Warzeski
ca64cc614a feat: redux hooks 2023-05-15 10:50:19 -04:00
Ben Warzeski
1ad297c46c refactor: testing architecture updates 2023-05-15 10:50:17 -04:00
Ben Warzeski
f2bb0d7c2a refactor: interventsions report component 2023-05-15 10:49:45 -04:00
Ben Warzeski
f76f3d64c9 refactor: bulk management controls component 2023-05-15 10:49:45 -04:00
Ben Warzeski
db56d76d37 refactor: gradebook header component refactor 2023-05-15 10:49:44 -04:00
36 changed files with 25607 additions and 5440 deletions

1
.env
View File

@@ -10,7 +10,6 @@ DATA_API_BASE_URL=''
SEGMENT_KEY=''
FEATURE_FLAGS={}
ACCESS_TOKEN_COOKIE_NAME=''
LANGUAGE_PREFERENCE_COOKIE_NAME=''
NEW_RELIC_APP_ID=''
NEW_RELIC_LICENSE_KEY=''
SITE_NAME=''

View File

@@ -12,7 +12,6 @@ FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
USER_INFO_COOKIE_NAME='edx-user-info'
SITE_NAME=localhost
DATA_API_BASE_URL='http://localhost:8000'

View File

@@ -11,18 +11,22 @@ on:
jobs:
test:
runs-on: ubuntu-20.04
strategy:
matrix:
node: [16]
npm: [8.5.x]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
uses: actions/checkout@v2
- name: Setup Nodejs
uses: actions/setup-node@v3
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VER }}
node-version: ${{ matrix.node }}
- name: Install npm 8.5.x
run: npm install -g npm@${{ matrix.npm }}
- name: Install dependencies
run: npm ci

View File

@@ -10,4 +10,4 @@ on:
jobs:
version-check:
uses: openedx/.github/.github/workflows/lockfileversion-check-v3.yml@master
uses: openedx/.github/.github/workflows/lockfileversion-check.yml@master

View File

@@ -15,13 +15,10 @@ jobs:
with:
fetch-depth: 0
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VER }}
node-version: 12
- name: Install dependencies
run: npm ci

1
.nvmrc
View File

@@ -1 +0,0 @@
18.15

View File

@@ -68,10 +68,9 @@ pull_translations:
&& atlas pull --filter=$(transifex_langs) \
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
translations/frontend-component-header/src/i18n/messages:frontend-component-header \
translations/paragon/src/i18n/messages:paragon \
translations/frontend-app-gradebook/src/i18n/messages:frontend-app-gradebook
$(intl_imports) paragon frontend-component-header frontend-component-footer frontend-app-gradebook
$(intl_imports) frontend-component-header frontend-component-footer frontend-app-gradebook
endif
# This target is used by CI.

30685
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -29,11 +29,10 @@
],
"dependencies": {
"@edx/brand": "npm:@edx/brand-openedx@^1.2.0",
"@edx/frontend-component-footer": "12.2.0",
"@edx/frontend-component-header": "4.6.0",
"@edx/frontend-platform": "5.5.4",
"@edx/paragon": "20.45.0",
"@edx/react-unit-test-utils": "1.7.0",
"@edx/frontend-component-footer": "^12.0.0",
"@edx/frontend-component-header": "^4.0.0",
"@edx/frontend-platform": "^4.2.0",
"@edx/paragon": "^19.25.4",
"@edx/reactifex": "^2.1.1",
"@fortawesome/fontawesome-svg-core": "^1.2.25",
"@fortawesome/free-brands-svg-icons": "^5.11.2",
@@ -50,12 +49,12 @@
"history": "4.10.1",
"prop-types": "15.8.1",
"query-string": "6.13.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react": "16.14.0",
"react-dom": "16.14.0",
"react-helmet": "^6.1.0",
"react-redux": "^7.2.9",
"react-router": "6.15.0",
"react-router-dom": "6.15.0",
"react-redux": "^7.1.1",
"react-router": "5.2.0",
"react-router-dom": "5.2.0",
"react-router-redux": "^5.0.0-alpha.9",
"redux": "4.0.5",
"redux-beacon": "^2.1.0",
@@ -68,18 +67,18 @@
},
"devDependencies": {
"@edx/browserslist-config": "^1.1.1",
"@edx/frontend-build": "12.9.3",
"@testing-library/react": "12.1.5",
"@wojtekmaj/enzyme-adapter-react-17": "0.8.0",
"@edx/frontend-build": "^12.4.15",
"@testing-library/react": "^12.1.0",
"axios": "0.21.2",
"axios-mock-adapter": "^1.17.0",
"enzyme-adapter-react-16": "^1.14.0",
"es-check": "^2.3.0",
"fetch-mock": "^6.5.2",
"husky": "2.7.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^26.6.3",
"react-dev-utils": "^12.0.1",
"react-test-renderer": "17.0.2",
"react-test-renderer": "^16.10.1",
"reactifex": "1.1.1",
"redux-mock-store": "^1.5.3",
"semantic-release": "^19.0.3"

View File

@@ -1,11 +1,12 @@
import React from 'react';
import { Route, Routes } from 'react-router-dom';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { AppProvider } from '@edx/frontend-platform/react';
import Footer from '@edx/frontend-component-footer';
import Header from '@edx/frontend-component-header';
import { routePath } from 'data/constants/app';
import store from 'data/store';
import GradebookPage from 'containers/GradebookPage';
import './App.scss';
@@ -14,18 +15,21 @@ import Head from './head/Head';
const App = () => (
<AppProvider store={store}>
<Head />
<div>
<Header />
<main>
<Routes>
<Route
path="/:courseId"
element={<GradebookPage />}
/>
</Routes>
</main>
<Footer logo={process.env.LOGO_POWERED_BY_OPEN_EDX_URL_SVG} />
</div>
<Router>
<div>
<Header />
<main>
<Switch>
<Route
exact
path={routePath}
component={GradebookPage}
/>
</Switch>
</main>
<Footer logo={process.env.LOGO_POWERED_BY_OPEN_EDX_URL_SVG} />
</div>
</Router>
</AppProvider>
);

View File

@@ -1,12 +1,13 @@
import React from 'react';
import { shallow } from 'enzyme';
import { Route, Routes } from 'react-router-dom';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { AppProvider } from '@edx/frontend-platform/react';
import Footer from '@edx/frontend-component-footer';
import Header from '@edx/frontend-component-header';
import { routePath } from 'data/constants/app';
import store from 'data/store';
import GradebookPage from 'containers/GradebookPage';
@@ -16,10 +17,14 @@ import Head from './head/Head';
jest.mock('react-router-dom', () => ({
BrowserRouter: () => 'BrowserRouter',
Route: () => 'Route',
Switch: () => 'Switch',
}));
jest.mock('@edx/frontend-platform/react', () => ({
AppProvider: () => 'AppProvider',
}));
jest.mock('data/constants/app', () => ({
routePath: '/:courseId',
}));
jest.mock('@edx/frontend-component-footer', () => 'Footer');
jest.mock('data/store', () => 'testStore');
jest.mock('containers/GradebookPage', () => 'GradebookPage');
@@ -27,7 +32,7 @@ jest.mock('@edx/frontend-component-header', () => 'Header');
const logo = 'fakeLogo.png';
let el;
let secondChild;
let router;
describe('App router component', () => {
test('snapshot', () => {
@@ -37,7 +42,7 @@ describe('App router component', () => {
beforeEach(() => {
process.env.LOGO_POWERED_BY_OPEN_EDX_URL_SVG = logo;
el = shallow(<App />);
secondChild = el.childAt(1);
router = el.childAt(1);
});
describe('AppProvider', () => {
test('AppProvider is the parent component, passed the redux store props', () => {
@@ -52,24 +57,24 @@ describe('App router component', () => {
});
describe('Router', () => {
test('second child of AppProvider', () => {
expect(secondChild.type()).toBe('div');
expect(router.type()).toBe(Router);
});
test('Header is above/outside-of the routing', () => {
expect(secondChild.childAt(0).type()).toBe(Header);
expect(secondChild.childAt(1).type()).toBe('main');
expect(router.childAt(0).childAt(0).type()).toBe(Header);
expect(router.childAt(0).childAt(1).type()).toBe('main');
});
test('Routing - GradebookPage is only route', () => {
expect(secondChild.find('main')).toEqual(shallow(
expect(router.find('main')).toEqual(shallow(
<main>
<Routes>
<Route path="/:courseId" element={<GradebookPage />} />
</Routes>
<Switch>
<Route exact path={routePath} component={GradebookPage} />
</Switch>
</main>,
));
});
});
test('Footer logo drawn from env variable', () => {
expect(secondChild.find(Footer).props().logo).toEqual(logo);
expect(router.find(Footer).props().logo).toEqual(logo);
});
});
});

View File

@@ -5,17 +5,20 @@ exports[`App router component snapshot 1`] = `
store="testStore"
>
<Head />
<div>
<Header />
<main>
<Component>
<Route
element={<GradebookPage />}
path="/:courseId"
/>
</Component>
</main>
<Footer />
</div>
<BrowserRouter>
<div>
<Header />
<main>
<Switch>
<Route
component="GradebookPage"
exact={true}
path="/:courseId"
/>
</Switch>
</main>
<Footer />
</div>
</BrowserRouter>
</AppProvider>
`;

View File

@@ -21,9 +21,7 @@ exports[`GradebookHeader component render default view shapshot 1`] = `
<div
className="subtitle-row d-flex justify-content-between align-items-center"
>
<h2
className="text-break"
>
<h2>
test-course-id
</h2>
</div>
@@ -51,9 +49,7 @@ exports[`GradebookHeader component render frozen grades snapshot: show frozen wa
<div
className="subtitle-row d-flex justify-content-between align-items-center"
>
<h2
className="text-break"
>
<h2>
test-course-id
</h2>
</div>
@@ -87,9 +83,7 @@ exports[`GradebookHeader component render show bulk management snapshot: show to
<div
className="subtitle-row d-flex justify-content-between align-items-center"
>
<h2
className="text-break"
>
<h2>
test-course-id
</h2>
<Button
@@ -123,9 +117,7 @@ exports[`GradebookHeader component render user cannot view gradebook snapshot: s
<div
className="subtitle-row d-flex justify-content-between align-items-center"
>
<h2
className="text-break"
>
<h2>
test-course-id
</h2>
</div>

View File

@@ -26,7 +26,7 @@ export const GradebookHeader = () => {
</a>
<h1>{formatMessage(messages.gradebook)}</h1>
<div className="subtitle-row d-flex justify-content-between align-items-center">
<h2 className="text-break">{courseId}</h2>
<h2>{courseId}</h2>
{showBulkManagement && (
<Button variant="tertiary" onClick={handleToggleViewClick}>
{formatMessage(toggleViewMessage)}

View File

@@ -9,7 +9,7 @@ const useOverrideTableData = () => {
const { formatMessage } = useIntl();
const hide = selectors.grades.useHasOverrideErrors();
const gradeOverrides = selectors.grades.useGradeData().gradeOverrideHistoryResults || [];
const gradeOverrides = selectors.grades.useGradeData().gradeOverrideHistoryResults;
const tableProps = {};
if (!hide) {
tableProps.columns = [

View File

@@ -7,7 +7,6 @@ exports[`FilterMenuToggle component render snapshot 1`] = `
onClick={[MockFunction hooks.toggleFilterMenu]}
>
<Icon
className="mr-1"
src="FilterAlt"
/>

View File

@@ -21,7 +21,7 @@ export const FilterMenuToggle = () => {
className="btn-primary align-self-start"
onClick={toggleFilterMenu}
>
<Icon src={FilterAlt} className="mr-1" /> {formatMessage(messages.editFilters)}
<Icon src={FilterAlt} /> {formatMessage(messages.editFilters)}
</Button>
);
};

View File

@@ -11,7 +11,7 @@ export const useGradeButtonData = ({ entry, subsection }) => {
const areGradesFrozen = selectors.assignmentTypes.useAreGradesFrozen();
const { gradeFormat } = selectors.grades.useGradeData();
const setModalState = thunkActions.app.useSetModalStateFromTable();
const label = transforms.grades.subsectionGrade({ gradeFormat, subsection })();
const label = transforms.grades.subsectionGrade({ gradeFormat, subsection });
const onClick = () => {
setModalState({

View File

@@ -41,7 +41,7 @@ const props = {
};
const gradeFormat = 'percent';
const setModalState = jest.fn();
const subsectionGrade = () => 'test-subsection-grade';
const subsectionGrade = 'test-subsection-grade';
selectors.assignmentTypes.useAreGradesFrozen.mockReturnValue(false);
selectors.grades.useGradeData.mockReturnValue({ gradeFormat });
thunkActions.app.useSetModalStateFromTable.mockReturnValue(setModalState);
@@ -73,7 +73,7 @@ describe('GradeButton', () => {
expect(out.areGradesFrozen).toEqual(false);
});
test('label passed from subsection grade redux hook', () => {
expect(out.label).toEqual(subsectionGrade());
expect(out.label).toEqual(subsectionGrade);
});
test('onClick sets modal state with user entry and subsection', () => {
out.onClick();

View File

@@ -39,7 +39,7 @@ export const useGradebookTableData = () => {
[Headings.username]: (
<Fields.Username username={entry.username} userKey={entry.external_user_key} />
),
[Headings.email]: (<Fields.Text value={entry.email} />),
[Headings.email]: (<Fields.Email email={entry.email} />),
[Headings.totalGrade]: `${roundGrade(entry.percent * 100)}${getLocalizedPercentSign()}`,
...entry.section_breakdown.reduce((acc, subsection) => ({
...acc,

View File

@@ -22,7 +22,7 @@ jest.mock('i18n/utils', () => ({
jest.mock('./GradeButton', () => 'GradeButton');
jest.mock('./Fields', () => jest.requireActual('testUtils').mockNestedComponents({
Username: 'Fields.Username',
Text: 'Fields.Text',
Email: 'Fields.Email',
}));
jest.mock('./LabelReplacements', () => jest.requireActual('testUtils').mockNestedComponents({
TotalGradeLabelReplacement: 'LabelReplacements.TotalGradeLabelReplacement',
@@ -158,7 +158,7 @@ describe('useGradebookTableData', () => {
test('email field', () => {
allGrades.forEach((entry, index) => {
expect(out.data[index][Headings.email]).toMatchObject(
<Fields.Text value={entry.email} />,
<Fields.Email email={entry.email} />,
);
});
});

View File

@@ -46,7 +46,6 @@
}
.grade-history-header{
float: left;
min-width: 170px;
}
.grade-history-assignment{
@@ -66,7 +65,7 @@
.gradebook-container {
width: 100%;
overflow-x: auto;
max-height: 600px;
height: 600px;
overflow-y: auto;
position: relative;
}
@@ -123,34 +122,3 @@ select#ScoreView.form-control {
border-right-color: $black;
}
}
#edit-filters-btn {
@include media-breakpoint-down(xs) {
width: 100%;
margin-bottom: 1rem;
}
}
.search-container {
@include media-breakpoint-down(xs) {
width: 100%;
}
}
.pgn__modal-body-content .pgn__data-table-layout-wrapper {
@include media-breakpoint-down(sm) {
clear: both;
padding: 1rem 0;
}
}
.page-gradebook {
position: relative;
.sidebar-container {
position: relative;
}
aside.sidebar {
overflow: auto;
}
}

View File

@@ -1,24 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ImportGradesButton component render Form 1`] = `
<Form
action="test-grade-export-url"
method="post"
>
<Form.Group
controlId="csv"
>
<Form.Control
className="d-none"
data-testid="file-control"
label="Upload Grade CSV"
onChange={[MockFunction props.handleFileInputChange]}
type="file"
/>
</Form.Group>
</Form>
`;
exports[`ImportGradesButton component render snapshot 1`] = `
<Fragment>
<Form
@@ -32,7 +13,7 @@ exports[`ImportGradesButton component render snapshot 1`] = `
className="d-none"
data-testid="file-control"
label="Upload Grade CSV"
onChange={[MockFunction props.handleFileInputChange]}
onChange={[MockFunction]}
type="file"
/>
</Form.Group>
@@ -47,7 +28,7 @@ exports[`ImportGradesButton component render snapshot 1`] = `
"id": "gradebook.GradesView.importGradesBtnText",
}
}
onClick={[MockFunction props.handleClickImportGrades]}
onClick={[MockFunction]}
/>
</Fragment>
`;

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import { shallow } from 'enzyme';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Form } from '@edx/paragon';
@@ -18,8 +18,8 @@ describe('ImportGradesButton component', () => {
props = {
fileInputRef: { current: null },
gradeExportUrl: 'test-grade-export-url',
handleClickImportGrades: jest.fn().mockName('props.handleClickImportGrades'),
handleFileInputChange: jest.fn().mockName('props.handleFileInputChange'),
handleClickImportGrades: jest.fn(),
handleFileInputChange: jest.fn(),
};
useImportGradesButtonData.mockReturnValue(props);
el = shallow(<ImportGradesButton />);
@@ -32,15 +32,14 @@ describe('ImportGradesButton component', () => {
});
describe('render', () => {
test('snapshot', () => {
expect(el.snapshot).toMatchSnapshot();
expect(el).toMatchSnapshot();
});
test('Form', () => {
expect(el.instance.findByType(Form)[0].snapshot).toMatchSnapshot();
expect(el.instance.findByType(Form)[0].props.action).toEqual(props.gradeExportUrl);
expect(el.instance.findByType(Form.Control)[0].props.onChange).toEqual(props.handleFileInputChange);
expect(el.find(Form).props().action).toEqual(props.gradeExportUrl);
expect(el.find(Form.Control).props().onChange).toEqual(props.handleFileInputChange);
});
test('import button', () => {
expect(el.instance.findByType(NetworkButton)[0].props.onClick).toEqual(props.handleClickImportGrades);
expect(el.find(NetworkButton).props().onClick).toEqual(props.handleClickImportGrades);
});
});
});

View File

@@ -1,9 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SearchControls component render snapshot 1`] = `
<div
className="search-container"
>
<div>
<SearchField
inputLabel="test-input-label"
onBlur={[MockFunction hooks.onBlur]}

View File

@@ -18,7 +18,7 @@ export const SearchControls = () => {
} = useSearchControlsData();
return (
<div className="search-container">
<div>
<SearchField
onSubmit={onSubmit}
inputLabel={inputLabel}

View File

@@ -10,7 +10,7 @@ exports[`GradesView component render snapshot 1`] = `
filter-step-heading
</h3>
<div
className="d-flex justify-content-between flex-wrap"
className="d-flex justify-content-between"
>
<FilterMenuToggle />
<SearchControls />

View File

@@ -34,7 +34,7 @@ export const GradesView = ({ updateQueryParams }) => {
{stepHeadings.filter}
</h3>
<div className="d-flex justify-content-between flex-wrap">
<div className="d-flex justify-content-between">
<FilterMenuToggle />
<SearchControls />
</div>

View File

@@ -14,8 +14,6 @@ import GradesView from 'components/GradesView';
import GradebookFilters from 'components/GradebookFilters';
import BulkManagementHistoryView from 'components/BulkManagementHistoryView';
import { withParams, withNavigate, withLocation } from '../../utils/hoc';
/**
* <GradebookPage />
* Top-level view for the Gradebook MFE.
@@ -30,11 +28,10 @@ export class GradebookPage extends React.Component {
componentDidMount() {
const urlQuery = queryString.parse(this.props.location.search);
this.props.initializeApp(this.props.courseId, urlQuery);
this.props.initializeApp(this.props.match.params.courseId, urlQuery);
}
updateQueryParams(queryParams) {
const { pathname } = this.props.location;
const parsed = queryString.parse(this.props.location.search);
Object.keys(queryParams).forEach((key) => {
if (queryParams[key]) {
@@ -43,7 +40,7 @@ export class GradebookPage extends React.Component {
delete parsed[key];
}
});
this.props.navigate({ pathname, search: `?${queryString.stringify(parsed)}` });
this.props.history.push(`?${queryString.stringify(parsed)}`);
}
render() {
@@ -63,12 +60,18 @@ export class GradebookPage extends React.Component {
}
}
GradebookPage.defaultProps = {
location: { pathname: '/', search: '' },
location: { search: '' },
};
GradebookPage.propTypes = {
navigate: PropTypes.func.isRequired,
location: PropTypes.shape({ pathname: PropTypes.string, search: PropTypes.string }),
courseId: PropTypes.string.isRequired,
history: PropTypes.shape({
push: PropTypes.func,
}).isRequired,
location: PropTypes.shape({ search: PropTypes.string }),
match: PropTypes.shape({
params: PropTypes.shape({
courseId: PropTypes.string,
}),
}).isRequired,
// redux
activeView: PropTypes.string.isRequired,
initializeApp: PropTypes.func.isRequired,
@@ -82,4 +85,4 @@ export const mapDispatchToProps = {
initializeApp: thunkActions.app.initialize,
};
export default connect(mapStateToProps, mapDispatchToProps)(withParams(withNavigate(withLocation(GradebookPage))));
export default connect(mapStateToProps, mapDispatchToProps)(GradebookPage);

View File

@@ -50,15 +50,14 @@ describe('GradebookPage', () => {
let el;
const props = {
location: {
pathname: '/',
search: 'searchString',
},
courseId,
match: { params: { courseId } },
activeView: views.grades,
};
beforeEach(() => {
props.initializeApp = jest.fn();
props.navigate = jest.fn();
props.history = { push: jest.fn() };
});
test('snapshot - shows BulkManagementHistoryView if activeView === views.bulkManagementHistory', () => {
el = shallow(<GradebookPage {...props} activeView={views.bulkManagementHistory} />);
@@ -131,7 +130,7 @@ describe('GradebookPage', () => {
const val2 = 'VALTWO!!';
const args = { [newKey]: val1, [props.location.search]: val2 };
el.instance().updateQueryParams(args);
expect(props.navigate).toHaveBeenCalledWith({ pathname: '/', search: `?${queryString.stringify(args)}` });
expect(props.history.push).toHaveBeenCalledWith(`?${queryString.stringify(args)}`);
});
it('clears values for non-truthy values', () => {
queryString.parse.mockImplementation(key => ({ [key]: key }));
@@ -140,8 +139,8 @@ describe('GradebookPage', () => {
const val2 = false;
const args = { [newKey]: val1, [props.location.search]: val2 };
el.instance().updateQueryParams(args);
expect(props.navigate).toHaveBeenCalledWith(
{ pathname: '/', search: `?${queryString.stringify({ [newKey]: val1 })}` },
expect(props.history.push).toHaveBeenCalledWith(
`?${queryString.stringify({ [newKey]: val1 })}`,
);
});
});

View File

@@ -1,4 +1,7 @@
import { StrictDict } from 'utils';
import { getConfig } from '@edx/frontend-platform';
export const routePath = `${getConfig().PUBLIC_PATH}:courseId`;
export const views = StrictDict({
grades: 'grades',

View File

@@ -10,7 +10,7 @@ import { fetchGrades } from './grades';
import { fetchTracks } from './tracks';
import { fetchAssignmentTypes } from './assignmentTypes';
export const allowedRoles = ['staff', 'limited_staff', 'instructor', 'support'];
export const allowedRoles = ['staff', 'instructor', 'support'];
export const fetchRoles = () => (
(dispatch, getState) => {

View File

@@ -1,6 +1,5 @@
import { messages as footerMessages } from '@edx/frontend-component-footer';
import { messages as headerMessages } from '@edx/frontend-component-header';
import { messages as paragonMessages } from '@edx/paragon';
import arMessages from './messages/ar.json';
import deMessages from './messages/de.json';
@@ -32,7 +31,6 @@ const appMessages = {
};
export default [
paragonMessages,
footerMessages,
headerMessages,
appMessages,

View File

@@ -1,7 +1,7 @@
/* eslint-disable import/no-extraneous-dependencies */
import Enzyme from 'enzyme';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });

View File

@@ -1,24 +0,0 @@
import React from 'react';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
export const withParams = WrappedComponent => {
const WithParamsComponent = props => <WrappedComponent {...useParams()} {...props} />;
return WithParamsComponent;
};
export const withNavigate = WrappedComponent => {
const WithNavigateComponent = props => {
const navigate = useNavigate();
return <WrappedComponent navigate={navigate} {...props} />;
};
return WithNavigateComponent;
};
export const withLocation = WrappedComponent => {
const WithLocationComponent = props => {
const location = useLocation();
return <WrappedComponent location={location} {...props} />;
};
return WithLocationComponent;
};

View File

@@ -1,38 +0,0 @@
import React from 'react';
import { mount } from 'enzyme';
import { withLocation, withNavigate } from './hoc';
const mockedNavigator = jest.fn();
jest.mock('react-router-dom', () => ({
useNavigate: () => mockedNavigator,
useLocation: () => ({
pathname: '/current-location',
}),
}));
// eslint-disable-next-line react/prop-types
const MockComponent = ({ navigate, location }) => (
// eslint-disable-next-line react/button-has-type, react/prop-types
<button id="btn" onClick={() => navigate('/some-route')}>{location.pathname}</button>
);
const WrappedComponent = withNavigate(withLocation(MockComponent));
test('Provide Navigation to Component', () => {
const wrapper = mount(
<WrappedComponent />,
);
const btn = wrapper.find('#btn');
btn.simulate('click');
expect(mockedNavigator).toHaveBeenCalledWith('/some-route');
});
test('Provide Location object to Component', () => {
const wrapper = mount(
<WrappedComponent />,
);
expect(wrapper.find('#btn').text()).toContain('/current-location');
});