Compare commits
64 Commits
release/te
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee3e4733ff | ||
|
|
c442df7c6d | ||
|
|
b7f387c45e | ||
|
|
966d7dc849 | ||
|
|
606d5ce631 | ||
|
|
f48db33355 | ||
|
|
8f9c5f4247 | ||
|
|
4759819071 | ||
|
|
f89963a0e1 | ||
|
|
6daa3b3cec | ||
|
|
8cb2fc18ff | ||
|
|
65d82ece23 | ||
|
|
5087bbb0e9 | ||
|
|
81aa35edd6 | ||
|
|
45e81aff46 | ||
|
|
1e1ccda82e | ||
|
|
2af3703d68 | ||
|
|
17fa6e53b5 | ||
|
|
1c886d9c21 | ||
|
|
1e1250f93a | ||
|
|
cae75368bf | ||
|
|
ea2f1f9f6f | ||
|
|
a4f63de389 | ||
|
|
6e1688d627 | ||
|
|
e759221e5f | ||
|
|
02a3bda10a | ||
|
|
aa4ddfa977 | ||
|
|
2b88ef3144 | ||
|
|
cd6cb71eb5 | ||
|
|
c6d72bcf47 | ||
|
|
a0bdd0c012 | ||
|
|
73e1421a90 | ||
|
|
d38ec004cb | ||
|
|
03d4d403b7 | ||
|
|
95a0cafac4 | ||
|
|
4a221c9caa | ||
|
|
40d7167744 | ||
|
|
a2a3af4ea3 | ||
|
|
5aacd38010 | ||
|
|
a5aad38cff | ||
|
|
2456251790 | ||
|
|
34a657d212 | ||
|
|
99573f1d93 | ||
|
|
1f16468bee | ||
|
|
0225daf3d2 | ||
|
|
86ede70c41 | ||
|
|
afd688d198 | ||
|
|
8a82b60b22 | ||
|
|
b608be06fe | ||
|
|
00017e3be1 | ||
|
|
25f686a875 | ||
|
|
b88969c9bb | ||
|
|
427907fba2 | ||
|
|
a9608149db | ||
|
|
a4d1fb28aa | ||
|
|
42dbbee796 | ||
|
|
f0b6fc291e | ||
|
|
a9cceb1ef9 | ||
|
|
9921542f7e | ||
|
|
c31185acfd | ||
|
|
d5cdbb8047 | ||
|
|
941f27a2f4 | ||
|
|
5753412ede | ||
|
|
790de20613 |
2
.env
2
.env
@@ -34,3 +34,5 @@ ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM=''
|
|||||||
APP_ID=''
|
APP_ID=''
|
||||||
MFE_CONFIG_API_URL=''
|
MFE_CONFIG_API_URL=''
|
||||||
DISPLAY_FEEDBACK_WIDGET='true'
|
DISPLAY_FEEDBACK_WIDGET='true'
|
||||||
|
# Fallback in local style files
|
||||||
|
PARAGON_THEME_URLS={}
|
||||||
|
|||||||
@@ -40,3 +40,5 @@ ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='Footer'
|
|||||||
APP_ID=''
|
APP_ID=''
|
||||||
MFE_CONFIG_API_URL=''
|
MFE_CONFIG_API_URL=''
|
||||||
DISPLAY_FEEDBACK_WIDGET='false'
|
DISPLAY_FEEDBACK_WIDGET='false'
|
||||||
|
# Fallback in local style files
|
||||||
|
PARAGON_THEME_URLS={}
|
||||||
|
|||||||
6
.github/CODEOWNERS
vendored
6
.github/CODEOWNERS
vendored
@@ -1,6 +0,0 @@
|
|||||||
# Code owners for frontend-app-gradebook, editable gradebook micro-frontend (MFE)
|
|
||||||
|
|
||||||
# These owners will be the default owners for everything in
|
|
||||||
# the repo. Unless a later match takes precedence, they will
|
|
||||||
# be requested for review when someone opens a pull request.
|
|
||||||
* @openedx/content-aurora
|
|
||||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Send failure notification
|
- name: Send failure notification
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
uses: dawidd6/action-send-mail@v4
|
uses: dawidd6/action-send-mail@v6
|
||||||
with:
|
with:
|
||||||
server_address: email-smtp.us-east-1.amazonaws.com
|
server_address: email-smtp.us-east-1.amazonaws.com
|
||||||
server_port: 465
|
server_port: 465
|
||||||
|
|||||||
189
README.md
189
README.md
@@ -1,189 +0,0 @@
|
|||||||
# frontend-app-gradebook
|
|
||||||
|
|
||||||
[](https://travis-ci.com/edx/frontend-app-gradebook)
|
|
||||||
[](https://app.codecov.io/gh/openedx/frontend-app-gradebook)
|
|
||||||
[](@edx/frontend-app-gradebook)
|
|
||||||
[](@edx/frontend-app-gradebook)
|
|
||||||
[](@edx/frontend-app-gradebook)
|
|
||||||
[](https://github.com/semantic-release/semantic-release)
|
|
||||||
|
|
||||||
# Purpose
|
|
||||||
|
|
||||||
Gradebook allows course staff to view, filter, and override subsection grades for a course. Additionally for Masters courses, Gradebook enables bulk management of subsection grades.
|
|
||||||
|
|
||||||
Jump to:
|
|
||||||
|
|
||||||
- [Should I use Gradebook in my course?](#should-i-use-gradebook-in-my-course)
|
|
||||||
- [Quickstart](#quickstart)
|
|
||||||
|
|
||||||
For existing documentation see:
|
|
||||||
|
|
||||||
- Basic Usage: [Review Learner Grades (read-the-docs)](https://docs.openedx.org/en/latest/educators/how-tos/data/view_learner_grades.html)
|
|
||||||
- Bulk Grade Management: [Override Learner Subsection Scores in Bulk (read-the-docs)](https://docs.openedx.org/en/latest/educators/how-tos/data/manage_learner_grades.html#override-learner-subsection-scores-in-bulk)
|
|
||||||
|
|
||||||
## Should I use Gradebook in my course?
|
|
||||||
|
|
||||||
### What does this offer over the legacy gradebook?
|
|
||||||
|
|
||||||
The micro-frontend offers a great deal more granularity when searching for problems, an easy interface for editing grades, an
|
|
||||||
audit trail for seeing who edited what grade and what reason they gave (if any) for doing so.
|
|
||||||
|
|
||||||
UsageProblems can be filtered by student as in the traditional gradebook, but can also be filtered by scores to see who
|
|
||||||
scored within a certain range, and by assignment types (note: Not problem types, but categories like ‘Exams’ or
|
|
||||||
‘Homework’).
|
|
||||||
|
|
||||||
### What does the legacy gradebook offer that this project does not?
|
|
||||||
|
|
||||||
This project does not (yet, at least) create any graphs, which the traditional gradebook does. It also does not give
|
|
||||||
quick links to the problems for the instructor to visit. It expects the instructor to be familiar with the problems they
|
|
||||||
are grading and which unit they refer to.
|
|
||||||
|
|
||||||
The gradebook is expected to be much more performant for larger numbers of students as well. The Instructor Dashboard
|
|
||||||
link for the legacy gradebook reports that "this feature is available only to courses with a small number of enrolled
|
|
||||||
learners." However, this project comes with no such warning.
|
|
||||||
|
|
||||||
### Who should not change to this gradebook?
|
|
||||||
|
|
||||||
Groups whose instructors need not ever manually override grades do not need this project, but may not be any worse off
|
|
||||||
depending on their needs. Instructors that expect to review grades infrequently enough that not having a direct link
|
|
||||||
to the problem in question will have a worse UX than the legacy gradebook provides. Instructors that rely on the graphs
|
|
||||||
generated by the current gradebook might find the lack of autogenerated graphs to be frustrating.
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
To install gradebook into your project:
|
|
||||||
```
|
|
||||||
npm i --save @edx/frontend-app-gradebook
|
|
||||||
```
|
|
||||||
|
|
||||||
Cloning and Startup
|
|
||||||
===================
|
|
||||||
|
|
||||||
1. Clone your new repo:
|
|
||||||
|
|
||||||
``git clone https://github.com/openedx/frontend-app-gradebook.git``
|
|
||||||
|
|
||||||
2. Use the version of Node specified in ``.nvmrc``
|
|
||||||
|
|
||||||
3. Stop the Tutor devstack, if it's running:
|
|
||||||
|
|
||||||
``tutor dev stop``
|
|
||||||
|
|
||||||
4. Next, we need to tell Tutor that we're going to be running this repo in development mode, and it should be excluded from the mfe container that otherwise runs every MFE. Run this:
|
|
||||||
|
|
||||||
``tutor mounts add /path/to/frontend-app-gradebook``
|
|
||||||
|
|
||||||
5. Start Tutor in development mode. This command will start the LMS and Studio,
|
|
||||||
and other required MFEs like ``authn`` and ``account``, but will not start the
|
|
||||||
Gradebook MFE, which we're going to run on the host instead of in a container
|
|
||||||
managed by Tutor. Run:
|
|
||||||
|
|
||||||
``tutor dev start lms cms mfe``
|
|
||||||
|
|
||||||
## Startup
|
|
||||||
|
|
||||||
1. Install npm dependencies:
|
|
||||||
|
|
||||||
``cd frontend-app-gradebook && npm install``
|
|
||||||
|
|
||||||
2. Start the dev server:
|
|
||||||
|
|
||||||
``npm run dev``
|
|
||||||
|
|
||||||
## Running the UI Standalone
|
|
||||||
|
|
||||||
To install the project please refer to the [`MFE Development on Tutor`](https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development) instructions.
|
|
||||||
|
|
||||||
When not mounted, gradebook will run in the shared MFE container at http://apps.local.openedx.io/gradebook/course-v1:edX+DemoX+Demo_Course.
|
|
||||||
|
|
||||||
When mounted in the tutor ``gradebook`` container, or when running a local (host) webpack dev server, the web application runs on port **1994**, so when you go to `http://apps.local.openedx.io:1994/gradebook/course-v1:edX+DemoX+Demo_Course` you should see the UI (assuming you have such a Demo Course in your devstack). Note that you always have to provide a course id to actually see a gradebook.
|
|
||||||
|
|
||||||
(Note: This may not work in Tutor; these instructions are for the deprecated Devstack) You can see the log messages for the docker container by executing `make gradebook-logs` in the `devstack` directory.
|
|
||||||
|
|
||||||
Note that starting the container executes the `npm run start` script which will hot-reload JavaScript and Sass files changes, so you should (:crossed_fingers:) not need to do anything (other than wait) when making changes.
|
|
||||||
|
|
||||||
## Plugins
|
|
||||||
This MFE can be customized using [Frontend Plugin Framework](https://github.com/openedx/frontend-plugin-framework).
|
|
||||||
|
|
||||||
The parts of this MFE that can be customized in that manner are documented [here](/src/plugin-slots).
|
|
||||||
|
|
||||||
## Running tests
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
``nvm use``
|
|
||||||
|
|
||||||
``npm ci``
|
|
||||||
|
|
||||||
``npm test``
|
|
||||||
|
|
||||||
## Directory Structure
|
|
||||||
|
|
||||||
* `config`
|
|
||||||
* Directory for [`webpack`](https://webpack.js.org/) configurations
|
|
||||||
* `public`
|
|
||||||
* Entry point for the single-page application - `gradebook` has a single `index.html` file
|
|
||||||
* `src`
|
|
||||||
* `components`
|
|
||||||
* Directory for presentational `React` components
|
|
||||||
* `containers`
|
|
||||||
* Directory for container `React` components
|
|
||||||
* `data`
|
|
||||||
* `actions`
|
|
||||||
* Directory for `Redux` action creators
|
|
||||||
* `constants`
|
|
||||||
* `reducers`
|
|
||||||
* Directory for `Redux` reducers
|
|
||||||
|
|
||||||
## Authentication with backend API services
|
|
||||||
|
|
||||||
See the [`@edx/frontend-auth`](https://github.com/edx-unsupported/frontend-auth) repo for information about securing routes in your application that require user authentication.
|
|
||||||
|
|
||||||
License
|
|
||||||
=======
|
|
||||||
|
|
||||||
The code in this repository is licensed under the AGPLv3 unless otherwise
|
|
||||||
noted.
|
|
||||||
|
|
||||||
Contributing
|
|
||||||
============
|
|
||||||
|
|
||||||
Contributions are very welcome. Please read [How To Contribute](https://docs.openedx.org/en/latest/developers/references/developer_guide/process/index.html) for details.
|
|
||||||
|
|
||||||
This project is currently accepting all types of contributions, bug fixes,
|
|
||||||
security fixes, maintenance work, or new features. However, please make sure
|
|
||||||
to have a discussion about your new feature idea with the maintainers prior to
|
|
||||||
beginning development to maximize the chances of your change being accepted.
|
|
||||||
You can start a conversation by creating a new issue on this repo summarizing
|
|
||||||
your idea.
|
|
||||||
|
|
||||||
Getting Help
|
|
||||||
===========
|
|
||||||
|
|
||||||
If you're having trouble, we have discussion forums at
|
|
||||||
https://discuss.openedx.org where you can connect with others in the community.
|
|
||||||
|
|
||||||
Our real-time conversations are on Slack. You can request a [Slack
|
|
||||||
invitation](https://openedx.org/slack), then join our
|
|
||||||
[community Slack workspace](https://openedx.slack.com/) Because this is a
|
|
||||||
frontend repository, the best place to discuss it would be in the
|
|
||||||
[#wg-frontend channel](https://openedx.slack.com/archives/C04BM6YC7A6).
|
|
||||||
|
|
||||||
For anything non-trivial, the best path is to open an issue in this repository
|
|
||||||
with as many details about the issue you are facing as you can provide.
|
|
||||||
|
|
||||||
https://github.com/openedx/frontend-app-gradebook/issues
|
|
||||||
|
|
||||||
For more information about these options, see the [Getting Help](https://openedx.org/community/connect) page.
|
|
||||||
|
|
||||||
The Open edX Code of Conduct
|
|
||||||
============================
|
|
||||||
|
|
||||||
All community members are expected to follow the [Open edX Code of Conduct](https://openedx.org/code-of-conduct/).
|
|
||||||
|
|
||||||
Reporting Security Issues
|
|
||||||
=========================
|
|
||||||
Please do not report security issues in public. Please email security@openedx.org.
|
|
||||||
253
README.rst
Normal file
253
README.rst
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
frontend-app-gradebook
|
||||||
|
#######################
|
||||||
|
|
||||||
|
|Build Status| |Codecov| |npm_version| |npm_downloads| |license| |semantic-release|
|
||||||
|
|
||||||
|
Purpose
|
||||||
|
*******
|
||||||
|
|
||||||
|
Gradebook allows course staff to view, filter, and override subsection grades for a course. Additionally for Masters courses, Gradebook enables bulk management of subsection grades.
|
||||||
|
|
||||||
|
Jump to:
|
||||||
|
|
||||||
|
- `Should I use Gradebook in my course?`_
|
||||||
|
- Quickstart_
|
||||||
|
|
||||||
|
For existing documentation see:
|
||||||
|
|
||||||
|
- Basic Usage: `Review Learner Grades (read-the-docs) <https://docs.openedx.org/en/latest/educators/how-tos/data/view_learner_grades.html>`_
|
||||||
|
- Bulk Grade Management: `Override Learner Subsection Scores in Bulk (read-the-docs) <https://docs.openedx.org/en/latest/educators/how-tos/data/manage_learner_grades.html#override-learner-subsection-scores-in-bulk>`_
|
||||||
|
|
||||||
|
Should I use Gradebook in my course?
|
||||||
|
*************************************
|
||||||
|
|
||||||
|
What does this offer over the legacy gradebook?
|
||||||
|
================================================
|
||||||
|
|
||||||
|
The micro-frontend offers a great deal more granularity when searching for problems, an easy interface for editing grades, an
|
||||||
|
audit trail for seeing who edited what grade and what reason they gave (if any) for doing so.
|
||||||
|
|
||||||
|
UsageProblems can be filtered by student as in the traditional gradebook, but can also be filtered by scores to see who
|
||||||
|
scored within a certain range, and by assignment types (note: Not problem types, but categories like 'Exams' or
|
||||||
|
'Homework').
|
||||||
|
|
||||||
|
What does the legacy gradebook offer that this project does not?
|
||||||
|
=================================================================
|
||||||
|
|
||||||
|
This project does not (yet, at least) create any graphs, which the traditional gradebook does. It also does not give
|
||||||
|
quick links to the problems for the instructor to visit. It expects the instructor to be familiar with the problems they
|
||||||
|
are grading and which unit they refer to.
|
||||||
|
|
||||||
|
The gradebook is expected to be much more performant for larger numbers of students as well. The Instructor Dashboard
|
||||||
|
link for the legacy gradebook reports that "this feature is available only to courses with a small number of enrolled
|
||||||
|
learners." However, this project comes with no such warning.
|
||||||
|
|
||||||
|
Who should not change to this gradebook?
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
Groups whose instructors need not ever manually override grades do not need this project, but may not be any worse off
|
||||||
|
depending on their needs. Instructors that expect to review grades infrequently enough that not having a direct link
|
||||||
|
to the problem in question will have a worse UX than the legacy gradebook provides. Instructors that rely on the graphs
|
||||||
|
generated by the current gradebook might find the lack of autogenerated graphs to be frustrating.
|
||||||
|
|
||||||
|
Getting Started
|
||||||
|
***************
|
||||||
|
|
||||||
|
Installation
|
||||||
|
============
|
||||||
|
|
||||||
|
To install gradebook into your project:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
npm i --save @edx/frontend-app-gradebook
|
||||||
|
|
||||||
|
Quickstart
|
||||||
|
==========
|
||||||
|
|
||||||
|
Cloning and Setup
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
1. Clone your new repo:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
git clone https://github.com/openedx/frontend-app-gradebook.git
|
||||||
|
|
||||||
|
2. Use the version of Node specified in ``.nvmrc``
|
||||||
|
|
||||||
|
3. Stop the Tutor devstack, if it's running:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
tutor dev stop
|
||||||
|
|
||||||
|
4. Next, we need to tell Tutor that we're going to be running this repo in development mode, and it should be excluded from the mfe container that otherwise runs every MFE. Run this:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
tutor mounts add /path/to/frontend-app-gradebook
|
||||||
|
|
||||||
|
5. Start Tutor in development mode. This command will start the LMS and Studio,
|
||||||
|
and other required MFEs like ``authn`` and ``account``, but will not start the
|
||||||
|
Gradebook MFE, which we're going to run on the host instead of in a container
|
||||||
|
managed by Tutor. Run:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
tutor dev start lms cms mfe
|
||||||
|
|
||||||
|
Startup
|
||||||
|
-------
|
||||||
|
|
||||||
|
1. Install npm dependencies:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
cd frontend-app-gradebook && npm install
|
||||||
|
|
||||||
|
2. Start the dev server:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
Running the UI Standalone
|
||||||
|
==========================
|
||||||
|
|
||||||
|
To install the project please refer to the `MFE Development on Tutor <https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development>`_ instructions.
|
||||||
|
|
||||||
|
When not mounted, gradebook will run in the shared MFE container at http://apps.local.openedx.io/gradebook/course-v1:edX+DemoX+Demo_Course.
|
||||||
|
|
||||||
|
When mounted in the tutor ``gradebook`` container, or when running a local (host) webpack dev server, the web application runs on port **1994**, so when you go to http://apps.local.openedx.io:1994/gradebook/course-v1:edX+DemoX+Demo_Course you should see the UI (assuming you have such a Demo Course in your devstack). Note that you always have to provide a course id to actually see a gradebook.
|
||||||
|
|
||||||
|
(Note: This may not work in Tutor; these instructions are for the deprecated Devstack) You can see the log messages for the docker container by executing ``make gradebook-logs`` in the ``devstack`` directory.
|
||||||
|
|
||||||
|
Note that starting the container executes the ``npm run start`` script which will hot-reload JavaScript and Sass files changes, so you should (:crossed_fingers:) not need to do anything (other than wait) when making changes.
|
||||||
|
|
||||||
|
Plugins
|
||||||
|
*******
|
||||||
|
|
||||||
|
This MFE can be customized using `Frontend Plugin Framework <https://github.com/openedx/frontend-plugin-framework>`_.
|
||||||
|
|
||||||
|
The parts of this MFE that can be customized in that manner are documented `here </src/plugin-slots>`_.
|
||||||
|
|
||||||
|
Running tests
|
||||||
|
*************
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
nvm use
|
||||||
|
|
||||||
|
npm ci
|
||||||
|
|
||||||
|
npm test
|
||||||
|
|
||||||
|
Directory Structure
|
||||||
|
*******************
|
||||||
|
|
||||||
|
* ``config``
|
||||||
|
|
||||||
|
* Directory for `webpack <https://webpack.js.org/>`_ configurations
|
||||||
|
|
||||||
|
* ``public``
|
||||||
|
|
||||||
|
* Entry point for the single-page application - ``gradebook`` has a single ``index.html`` file
|
||||||
|
|
||||||
|
* ``src``
|
||||||
|
|
||||||
|
* ``components``
|
||||||
|
|
||||||
|
* Directory for presentational ``React`` components
|
||||||
|
|
||||||
|
* ``containers``
|
||||||
|
|
||||||
|
* Directory for container ``React`` components
|
||||||
|
|
||||||
|
* ``data``
|
||||||
|
|
||||||
|
* ``actions``
|
||||||
|
|
||||||
|
* Directory for ``Redux`` action creators
|
||||||
|
|
||||||
|
* ``constants``
|
||||||
|
* ``reducers``
|
||||||
|
|
||||||
|
* Directory for ``Redux`` reducers
|
||||||
|
|
||||||
|
Authentication with backend API services
|
||||||
|
*****************************************
|
||||||
|
|
||||||
|
See the `@edx/frontend-auth <https://github.com/edx-unsupported/frontend-auth>`_ repo for information about securing routes in your application that require user authentication.
|
||||||
|
|
||||||
|
License
|
||||||
|
*******
|
||||||
|
|
||||||
|
The code in this repository is licensed under the AGPLv3 unless otherwise
|
||||||
|
noted.
|
||||||
|
|
||||||
|
Please see `LICENSE <LICENSE>`_ for details.
|
||||||
|
|
||||||
|
Contributing
|
||||||
|
************
|
||||||
|
|
||||||
|
Contributions are very welcome. Please read `How To Contribute`_ for details.
|
||||||
|
|
||||||
|
.. _How To Contribute: https://openedx.org/r/how-to-contribute
|
||||||
|
|
||||||
|
This project is currently accepting all types of contributions, bug fixes,
|
||||||
|
security fixes, maintenance work, or new features. However, please make sure
|
||||||
|
to have a discussion about your new feature idea with the maintainers prior to
|
||||||
|
beginning development to maximize the chances of your change being accepted.
|
||||||
|
You can start a conversation by creating a new issue on this repo summarizing
|
||||||
|
your idea.
|
||||||
|
|
||||||
|
Getting Help
|
||||||
|
************
|
||||||
|
|
||||||
|
If you're having trouble, we have discussion forums at
|
||||||
|
https://discuss.openedx.org where you can connect with others in the community.
|
||||||
|
|
||||||
|
Our real-time conversations are on Slack. You can request a `Slack
|
||||||
|
invitation`_, then join our `community Slack workspace`_. Because this is a
|
||||||
|
frontend repository, the best place to discuss it would be in the
|
||||||
|
`#wg-frontend channel`_.
|
||||||
|
|
||||||
|
For anything non-trivial, the best path is to open an issue in this repository
|
||||||
|
with as many details about the issue you are facing as you can provide.
|
||||||
|
|
||||||
|
https://github.com/openedx/frontend-app-gradebook/issues
|
||||||
|
|
||||||
|
For more information about these options, see the `Getting Help`_ page.
|
||||||
|
|
||||||
|
.. _Slack invitation: https://openedx.org/slack
|
||||||
|
.. _community Slack workspace: https://openedx.slack.com/
|
||||||
|
.. _#wg-frontend channel: https://openedx.slack.com/archives/C04BM6YC7A6
|
||||||
|
.. _Getting Help: https://openedx.org/community/connect
|
||||||
|
|
||||||
|
The Open edX Code of Conduct
|
||||||
|
*****************************
|
||||||
|
|
||||||
|
All community members are expected to follow the `Open edX Code of Conduct`_.
|
||||||
|
|
||||||
|
.. _Open edX Code of Conduct: https://openedx.org/code-of-conduct/
|
||||||
|
|
||||||
|
Reporting Security Issues
|
||||||
|
**************************
|
||||||
|
|
||||||
|
Please do not report security issues in public. Please email security@openedx.org.
|
||||||
|
|
||||||
|
.. |Build Status| image:: https://api.travis-ci.com/edx/frontend-app-gradebook.svg?branch=master
|
||||||
|
:target: https://travis-ci.com/edx/frontend-app-gradebook
|
||||||
|
.. |Codecov| image:: https://img.shields.io/codecov/c/gh/openedx/frontend-app-gradebook
|
||||||
|
:target: https://app.codecov.io/gh/openedx/frontend-app-gradebook
|
||||||
|
.. |npm_version| image:: https://img.shields.io/npm/v/@edx/frontend-app-gradebook.svg
|
||||||
|
:target: @edx/frontend-app-gradebook
|
||||||
|
.. |npm_downloads| image:: https://img.shields.io/npm/dt/@edx/frontend-app-gradebook.svg
|
||||||
|
:target: @edx/frontend-app-gradebook
|
||||||
|
.. |license| image:: https://img.shields.io/npm/l/@edx/frontend-app-gradebook.svg
|
||||||
|
:target: @edx/frontend-app-gradebook
|
||||||
|
.. |semantic-release| image:: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
|
||||||
|
:target: https://github.com/semantic-release/semantic-release
|
||||||
@@ -9,5 +9,6 @@ module.exports = createConfig('jest', {
|
|||||||
'src/segment.js',
|
'src/segment.js',
|
||||||
'src/postcss.config.js',
|
'src/postcss.config.js',
|
||||||
'testUtils', // don't unit test jest mocking tools
|
'testUtils', // don't unit test jest mocking tools
|
||||||
|
'testUtilsExtra', // don't unit test jest mocking tools
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
7626
package-lock.json
generated
7626
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@edx/frontend-app-gradebook",
|
"name": "@edx/frontend-app-gradebook",
|
||||||
"version": "1.6.2",
|
"version": "1.6.3",
|
||||||
"description": "edx editable gradebook-ui to manipulate grade overrides on subsections",
|
"description": "edx editable gradebook-ui to manipulate grade overrides on subsections",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -31,17 +31,15 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
|
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
|
||||||
"@edx/frontend-component-footer": "^14.6.0",
|
"@edx/frontend-component-footer": "^14.6.0",
|
||||||
"@edx/frontend-component-header": "^6.4.0",
|
"@edx/frontend-component-header": "^6.6.1",
|
||||||
"@edx/frontend-platform": "^8.3.1",
|
"@edx/frontend-platform": "^8.3.7",
|
||||||
"@edx/openedx-atlas": "^0.6.0",
|
"@edx/openedx-atlas": "^0.6.0",
|
||||||
"@edx/react-unit-test-utils": "^4.0.0",
|
|
||||||
"@edx/reactifex": "^2.1.1",
|
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.25",
|
"@fortawesome/fontawesome-svg-core": "^1.2.25",
|
||||||
"@fortawesome/free-brands-svg-icons": "^5.11.2",
|
"@fortawesome/free-brands-svg-icons": "^5.11.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.11.2",
|
"@fortawesome/free-solid-svg-icons": "^5.11.2",
|
||||||
"@fortawesome/react-fontawesome": "^0.1.5",
|
"@fortawesome/react-fontawesome": "^0.1.5",
|
||||||
"@openedx/frontend-plugin-framework": "^1.6.0",
|
"@openedx/frontend-plugin-framework": "^1.6.0",
|
||||||
"@openedx/paragon": "^22.16.0",
|
"@openedx/paragon": "^23.4.5",
|
||||||
"@redux-beacon/segment": "^1.0.0",
|
"@redux-beacon/segment": "^1.0.0",
|
||||||
"@reduxjs/toolkit": "^1.5.1",
|
"@reduxjs/toolkit": "^1.5.1",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
@@ -69,15 +67,16 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@edx/browserslist-config": "^1.1.1",
|
"@edx/browserslist-config": "^1.1.1",
|
||||||
"@openedx/frontend-build": "^14.3.3",
|
"@openedx/frontend-build": "^14.6.2",
|
||||||
"@testing-library/react": "^16.2.0",
|
"@testing-library/jest-dom": "^6.6.4",
|
||||||
|
"@testing-library/react": "^16.3.0",
|
||||||
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"es-check": "^2.3.0",
|
"es-check": "^2.3.0",
|
||||||
"fetch-mock": "^12.2.0",
|
"fetch-mock": "^12.2.0",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"react-dev-utils": "^12.0.1",
|
"react-dev-utils": "^12.0.1",
|
||||||
"react-test-renderer": "^18.3.1",
|
"react-test-renderer": "^18.3.1",
|
||||||
"reactifex": "1.1.1",
|
|
||||||
"redux-mock-store": "^1.5.3"
|
"redux-mock-store": "^1.5.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
// frontend-app-*/src/index.scss
|
// frontend-app-*/src/index.scss
|
||||||
@import "~@edx/brand/paragon/fonts";
|
@use "@openedx/paragon/styles/css/core/custom-media-breakpoints.css" as paragonCustomMediaBreakpoints;
|
||||||
@import "~@edx/brand/paragon/variables";
|
|
||||||
@import "~@openedx/paragon/scss/core/core";
|
|
||||||
@import "~@edx/brand/paragon/overrides";
|
|
||||||
|
|
||||||
$fa-font-path: "~font-awesome/fonts";
|
$fa-font-path: "~font-awesome/fonts";
|
||||||
@import "~font-awesome/scss/font-awesome";
|
@import "~font-awesome/scss/font-awesome";
|
||||||
|
|
||||||
$input-focus-box-shadow: $input-box-shadow; // hack to get upgrade to paragon 4.0.0 to work
|
$input-focus-box-shadow: var(--pgn-elevation-form-input-base); // hack to get upgrade to paragon 4.0.0 to work
|
||||||
|
|
||||||
@import "~@edx/frontend-component-header/dist/index";
|
@import "~@edx/frontend-component-header/dist/index";
|
||||||
@import "~@edx/frontend-component-footer/dist/_footer";
|
@import "~@edx/frontend-component-footer/dist/_footer";
|
||||||
|
|||||||
102
src/App.test.jsx
102
src/App.test.jsx
@@ -1,63 +1,63 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { render, screen } from '@testing-library/react';
|
||||||
|
|
||||||
import { Route } from 'react-router-dom';
|
|
||||||
|
|
||||||
import store from 'data/store';
|
|
||||||
import GradebookPage from 'containers/GradebookPage';
|
|
||||||
|
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
|
||||||
jest.mock('react-router-dom', () => ({
|
jest.mock('react-router-dom', () => ({
|
||||||
BrowserRouter: () => 'BrowserRouter',
|
Routes: ({ children }) => children,
|
||||||
Route: () => 'Route',
|
Route: ({ element }) => element,
|
||||||
Routes: () => 'Routes',
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform/react', () => ({
|
jest.mock('@edx/frontend-platform/react', () => ({
|
||||||
AppProvider: () => 'AppProvider',
|
AppProvider: ({ children }) => children,
|
||||||
}));
|
}));
|
||||||
jest.mock('@edx/frontend-component-footer', () => ({ FooterSlot: 'FooterSlot' }));
|
|
||||||
jest.mock('data/store', () => 'testStore');
|
|
||||||
jest.mock('containers/GradebookPage', () => 'GradebookPage');
|
|
||||||
jest.mock('@edx/frontend-component-header', () => 'Header');
|
|
||||||
jest.mock('./head/Head', () => 'Head');
|
|
||||||
|
|
||||||
let el;
|
jest.mock('@edx/frontend-component-header', () => ({
|
||||||
let secondChild;
|
__esModule: true,
|
||||||
|
default: () => <div>Header</div>,
|
||||||
|
}));
|
||||||
|
|
||||||
describe('App router component', () => {
|
jest.mock('@edx/frontend-component-footer', () => ({
|
||||||
test('snapshot', () => {
|
FooterSlot: () => <div>Footer</div>,
|
||||||
expect(shallow(<App />).snapshot).toMatchSnapshot();
|
}));
|
||||||
|
|
||||||
|
jest.mock('./head/Head', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: () => <div>Head</div>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('containers/GradebookPage', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: () => <div>Gradebook</div>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('App', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render(<App />);
|
||||||
});
|
});
|
||||||
describe('component', () => {
|
|
||||||
beforeEach(() => {
|
afterEach(() => {
|
||||||
el = shallow(<App />);
|
jest.clearAllMocks();
|
||||||
secondChild = el.instance.children;
|
});
|
||||||
});
|
|
||||||
describe('AppProvider', () => {
|
it('renders Head component', () => {
|
||||||
test('AppProvider is the parent component, passed the redux store props', () => {
|
const head = screen.getByText('Head');
|
||||||
expect(el.instance.type).toBe('AppProvider');
|
expect(head).toBeInTheDocument();
|
||||||
expect(el.instance.props.store).toEqual(store);
|
});
|
||||||
});
|
|
||||||
});
|
it('renders Header component', () => {
|
||||||
describe('Head', () => {
|
const header = screen.getByText('Header');
|
||||||
test('first child of AppProvider', () => {
|
expect(header).toBeInTheDocument();
|
||||||
expect(el.instance.children[0].type).toBe('Head');
|
});
|
||||||
});
|
|
||||||
});
|
it('renders Footer component', () => {
|
||||||
describe('Router', () => {
|
const footer = screen.getByText('Footer');
|
||||||
test('second child of AppProvider', () => {
|
expect(footer).toBeInTheDocument();
|
||||||
expect(secondChild[1].type).toBe('div');
|
});
|
||||||
});
|
|
||||||
test('Header is above/outside-of the routing', () => {
|
it('renders main content wrapper', () => {
|
||||||
expect(secondChild[1].children[0].type).toBe('Header');
|
const main = screen.getByRole('main');
|
||||||
expect(secondChild[1].children[1].type).toBe('main');
|
expect(main).toBeInTheDocument();
|
||||||
});
|
const gradebook = screen.getByText('Gradebook');
|
||||||
test('Routing - GradebookPage is only route', () => {
|
expect(gradebook).toBeInTheDocument();
|
||||||
expect(secondChild[1].findByType(Route)).toHaveLength(1);
|
|
||||||
expect(secondChild[1].findByType(Route)[0].props.path).toEqual('/:courseId');
|
|
||||||
expect(secondChild[1].findByType(Route)[0].props.element.type).toEqual(GradebookPage);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`App router component snapshot 1`] = `
|
|
||||||
<AppProvider
|
|
||||||
store="testStore"
|
|
||||||
>
|
|
||||||
<Head />
|
|
||||||
<div>
|
|
||||||
<Header />
|
|
||||||
<main>
|
|
||||||
<Routes>
|
|
||||||
<Route
|
|
||||||
element={<GradebookPage />}
|
|
||||||
path="/:courseId"
|
|
||||||
/>
|
|
||||||
</Routes>
|
|
||||||
</main>
|
|
||||||
<FooterSlot />
|
|
||||||
</div>
|
|
||||||
</AppProvider>
|
|
||||||
`;
|
|
||||||
@@ -1,20 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
|
||||||
import { Alert } from '@openedx/paragon';
|
|
||||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
|
||||||
|
|
||||||
import selectors from 'data/selectors';
|
import selectors from 'data/selectors';
|
||||||
import messages from './messages';
|
|
||||||
|
|
||||||
import { BulkManagementAlerts, mapStateToProps } from './BulkManagementAlerts';
|
import { BulkManagementAlerts, mapStateToProps } from './BulkManagementAlerts';
|
||||||
|
import { renderWithIntl, screen } from '../../testUtilsExtra';
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform/i18n', () => ({
|
|
||||||
defineMessages: m => m,
|
|
||||||
FormattedMessage: () => 'FormattedMessage',
|
|
||||||
}));
|
|
||||||
jest.mock('@openedx/paragon', () => ({
|
|
||||||
Alert: () => 'Alert',
|
|
||||||
}));
|
|
||||||
jest.mock('data/selectors', () => ({
|
jest.mock('data/selectors', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
default: {
|
default: {
|
||||||
@@ -29,47 +19,19 @@ const errorMessage = 'Oh noooooo';
|
|||||||
|
|
||||||
describe('BulkManagementAlerts', () => {
|
describe('BulkManagementAlerts', () => {
|
||||||
describe('component', () => {
|
describe('component', () => {
|
||||||
let el;
|
describe('states of the warnings', () => {
|
||||||
describe('no errer, no upload success', () => {
|
test('no alert shown', () => {
|
||||||
beforeEach(() => {
|
renderWithIntl(<BulkManagementAlerts bulkImportError="" uploadSuccess={false} />);
|
||||||
el = shallow(<BulkManagementAlerts />);
|
expect(document.querySelectorAll('.alert').length).toEqual(0);
|
||||||
});
|
});
|
||||||
test('snapshot - bulkImportError closed, success closed', () => {
|
test('Just success alert shown', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
renderWithIntl(<BulkManagementAlerts bulkImportError="" uploadSuccess />);
|
||||||
|
expect(document.querySelectorAll('.alert-success').length).toEqual(1);
|
||||||
});
|
});
|
||||||
test('closed danger alert', () => {
|
test('Just error alert shown', () => {
|
||||||
expect(el.instance.children[0].type).toBe('Alert');
|
renderWithIntl(<BulkManagementAlerts bulkImportError={errorMessage} uploadSuccess={false} />);
|
||||||
expect(el.instance.findByType(Alert)[0].props.show).toEqual(false);
|
expect(document.querySelectorAll('.alert-danger').length).toEqual(1);
|
||||||
expect(el.instance.findByType(Alert)[0].props.variant).toEqual('danger');
|
expect(screen.getByText(errorMessage)).toBeInTheDocument();
|
||||||
});
|
|
||||||
test('closed success alert', () => {
|
|
||||||
expect(el.instance.children[1].type).toBe('Alert');
|
|
||||||
expect(el.instance.findByType(Alert)[1].props.show).toEqual(false);
|
|
||||||
expect(el.instance.findByType(Alert)[1].props.variant).toEqual('success');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('no errer, no upload success', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
el = shallow(<BulkManagementAlerts uploadSuccess bulkImportError={errorMessage} />);
|
|
||||||
});
|
|
||||||
const assertions = [
|
|
||||||
'danger alert open with bulkImportError',
|
|
||||||
'success alert open with messages.successDialog',
|
|
||||||
];
|
|
||||||
test(`snapshot - ${assertions.join(', ')}`, () => {
|
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
test('open danger alert with bulkImportError content', () => {
|
|
||||||
expect(el.instance.children[0].type).toBe('Alert');
|
|
||||||
expect(el.instance.findByType(Alert)[0].children[0].el).toEqual(errorMessage);
|
|
||||||
expect(el.instance.findByType(Alert)[0].props.show).toEqual(true);
|
|
||||||
});
|
|
||||||
test('open success alert with messages.successDialog content', () => {
|
|
||||||
expect(el.instance.children[1].type).toBe('Alert');
|
|
||||||
expect(el.shallowWrapper.props.children[1].props.children).toEqual(
|
|
||||||
<FormattedMessage {...messages.successDialog} />,
|
|
||||||
);
|
|
||||||
expect(el.instance.children[1].props.show).toEqual(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,108 +1,187 @@
|
|||||||
/* eslint-disable import/no-named-as-default */
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { render, screen, initializeMocks } from 'testUtilsExtra';
|
||||||
import { DataTable } from '@openedx/paragon';
|
import { DataTable } from '@openedx/paragon';
|
||||||
|
|
||||||
import selectors from 'data/selectors';
|
import selectors from 'data/selectors';
|
||||||
import { bulkManagementColumns } from 'data/constants/app';
|
import { bulkManagementColumns } from 'data/constants/app';
|
||||||
|
|
||||||
|
import { HistoryTable, mapHistoryRows, mapStateToProps } from './HistoryTable';
|
||||||
import ResultsSummary from './ResultsSummary';
|
import ResultsSummary from './ResultsSummary';
|
||||||
import { HistoryTable, mapStateToProps } from './HistoryTable';
|
|
||||||
|
|
||||||
jest.mock('@openedx/paragon', () => ({ DataTable: () => 'DataTable' }));
|
initializeMocks();
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform/i18n', () => ({
|
jest.mock('@openedx/paragon', () => ({
|
||||||
defineMessages: m => m,
|
...jest.requireActual('@openedx/paragon'),
|
||||||
FormattedMessage: () => 'FormattedMessage',
|
DataTable: jest.fn(() => <div data-testid="data-table">DataTable</div>),
|
||||||
}));
|
}));
|
||||||
|
jest.mock('./ResultsSummary', () => jest.fn(() => <div data-testid="results-summary">ResultsSummary</div>));
|
||||||
jest.mock('data/selectors', () => ({
|
jest.mock('data/selectors', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
default: {
|
default: {
|
||||||
grades: {
|
grades: {
|
||||||
bulkManagementHistoryEntries: jest.fn(state => ({ historyEntries: state })),
|
bulkManagementHistoryEntries: jest.fn(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
jest.mock('./ResultsSummary', () => 'ResultsSummary');
|
|
||||||
|
|
||||||
describe('HistoryTable', () => {
|
describe('HistoryTable', () => {
|
||||||
describe('component', () => {
|
beforeEach(() => {
|
||||||
const entry1 = {
|
jest.clearAllMocks();
|
||||||
originalFilename: 'blue.png',
|
});
|
||||||
user: 'Eifel',
|
|
||||||
timeUploaded: '65',
|
const mockBulkManagementHistory = [
|
||||||
|
{
|
||||||
|
originalFilename: 'test-file-1.csv',
|
||||||
|
user: 'test-user-1',
|
||||||
|
timeUploaded: '2025-01-01T10:00:00Z',
|
||||||
resultsSummary: {
|
resultsSummary: {
|
||||||
rowId: 12,
|
rowId: 1,
|
||||||
courseId: 'Da Bu Dee',
|
text: 'Download results 1',
|
||||||
text: 'Da ba daa',
|
|
||||||
},
|
},
|
||||||
};
|
},
|
||||||
const entry2 = {
|
{
|
||||||
originalFilename: 'allStar.jpg',
|
originalFilename: 'test-file-2.csv',
|
||||||
user: 'Smashmouth',
|
user: 'test-user-2',
|
||||||
timeUploaded: '2000s?',
|
timeUploaded: '2025-01-02T10:00:00Z',
|
||||||
resultsSummary: {
|
resultsSummary: {
|
||||||
courseId: 'rockstar',
|
|
||||||
rowId: 2,
|
rowId: 2,
|
||||||
text: 'all that glitters is gold',
|
text: 'Download results 2',
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('mapHistoryRows', () => {
|
||||||
|
const mockRow = {
|
||||||
|
resultsSummary: {
|
||||||
|
rowId: 1,
|
||||||
|
text: 'Download results',
|
||||||
|
},
|
||||||
|
originalFilename: 'test-file.csv',
|
||||||
|
user: 'test-user',
|
||||||
|
timeUploaded: '2025-01-01T10:00:00Z',
|
||||||
};
|
};
|
||||||
const props = {
|
|
||||||
bulkManagementHistory: [entry1, entry2],
|
it('transforms row data correctly', () => {
|
||||||
};
|
const result = mapHistoryRows(mockRow);
|
||||||
let el;
|
|
||||||
describe('snapshot', () => {
|
expect(result).toHaveProperty('resultsSummary');
|
||||||
beforeEach(() => {
|
expect(result).toHaveProperty('filename');
|
||||||
el = shallow(<HistoryTable {...props} />);
|
expect(result).toHaveProperty('user');
|
||||||
});
|
expect(result).toHaveProperty('timeUploaded');
|
||||||
test('snapshot - loads formatted table', () => {
|
expect(result.timeUploaded).toBe('2025-01-01T10:00:00Z');
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
});
|
||||||
});
|
|
||||||
describe('history table', () => {
|
it('wraps filename in span with correct class', () => {
|
||||||
let table;
|
const result = mapHistoryRows(mockRow);
|
||||||
beforeEach(() => {
|
render(<div>{result.filename}</div>);
|
||||||
table = el.instance.findByType(DataTable);
|
|
||||||
});
|
const filenameSpan = screen.getByText('test-file.csv');
|
||||||
describe('data (from bulkManagementHistory.map(this.formatHistoryRow)', () => {
|
expect(filenameSpan).toBeInTheDocument();
|
||||||
const fieldAssertions = [
|
expect(filenameSpan).toHaveClass('wrap-text-in-cell');
|
||||||
'maps resultsSummay to ResultsSummary',
|
});
|
||||||
'wraps filename and user',
|
|
||||||
'forwards the rest',
|
it('wraps user in span with correct class', () => {
|
||||||
];
|
const result = mapHistoryRows(mockRow);
|
||||||
test(`snapshot: ${fieldAssertions.join(', ')}`, () => {
|
render(<div>{result.user}</div>);
|
||||||
expect(table[0].props.data).toMatchSnapshot();
|
|
||||||
});
|
const userSpan = screen.getByText('test-user');
|
||||||
test(fieldAssertions.join(', '), () => {
|
expect(userSpan).toBeInTheDocument();
|
||||||
const rows = table[0].props.data;
|
expect(userSpan).toHaveClass('wrap-text-in-cell');
|
||||||
expect(rows[0].resultsSummary).toEqual(<ResultsSummary {...entry1.resultsSummary} />);
|
});
|
||||||
expect(rows[0].user).toEqual(<span className="wrap-text-in-cell">{entry1.user}</span>);
|
|
||||||
expect(
|
it('renders ResultsSummary component with correct props', () => {
|
||||||
rows[0].filename,
|
const result = mapHistoryRows(mockRow);
|
||||||
).toEqual(<span className="wrap-text-in-cell">{entry1.originalFilename}</span>);
|
render(<div>{result.resultsSummary}</div>);
|
||||||
expect(rows[1].resultsSummary).toEqual(<ResultsSummary {...entry2.resultsSummary} />);
|
|
||||||
expect(rows[1].user).toEqual(<span className="wrap-text-in-cell">{entry2.user}</span>);
|
expect(ResultsSummary).toHaveBeenCalledWith(mockRow.resultsSummary, {});
|
||||||
expect(
|
expect(screen.getByTestId('results-summary')).toBeInTheDocument();
|
||||||
rows[1].filename,
|
});
|
||||||
).toEqual(<span className="wrap-text-in-cell">{entry2.originalFilename}</span>);
|
});
|
||||||
});
|
|
||||||
});
|
describe('component', () => {
|
||||||
test('columns from bulkManagementColumns', () => {
|
it('renders DataTable with empty data when no history provided', () => {
|
||||||
expect(table[0].props.columns).toEqual(bulkManagementColumns);
|
render(<HistoryTable />);
|
||||||
});
|
|
||||||
});
|
expect(DataTable).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
data: [],
|
||||||
|
hasFixedColumnWidths: true,
|
||||||
|
columns: bulkManagementColumns,
|
||||||
|
className: 'table-striped',
|
||||||
|
itemCount: 0,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('data-table')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders DataTable with mapped history data', () => {
|
||||||
|
render(
|
||||||
|
<HistoryTable bulkManagementHistory={mockBulkManagementHistory} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(DataTable).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
data: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
filename: expect.any(Object),
|
||||||
|
user: expect.any(Object),
|
||||||
|
resultsSummary: expect.any(Object),
|
||||||
|
timeUploaded: '2025-01-01T10:00:00Z',
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
filename: expect.any(Object),
|
||||||
|
user: expect.any(Object),
|
||||||
|
resultsSummary: expect.any(Object),
|
||||||
|
timeUploaded: '2025-01-02T10:00:00Z',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
hasFixedColumnWidths: true,
|
||||||
|
columns: bulkManagementColumns,
|
||||||
|
className: 'table-striped',
|
||||||
|
itemCount: 2,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes correct props to DataTable', () => {
|
||||||
|
render(
|
||||||
|
<HistoryTable bulkManagementHistory={mockBulkManagementHistory} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
const dataTableCall = DataTable.mock.calls[0][0];
|
||||||
|
expect(dataTableCall.hasFixedColumnWidths).toBe(true);
|
||||||
|
expect(dataTableCall.columns).toBe(bulkManagementColumns);
|
||||||
|
expect(dataTableCall.className).toBe('table-striped');
|
||||||
|
expect(dataTableCall.itemCount).toBe(mockBulkManagementHistory.length);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('mapStateToProps', () => {
|
describe('mapStateToProps', () => {
|
||||||
const testState = { a: 'simple', test: 'state' };
|
const mockState = { test: 'state' };
|
||||||
let mapped;
|
const mockHistoryEntries = [
|
||||||
|
{ originalFilename: 'file1.csv', user: 'user1' },
|
||||||
|
{ originalFilename: 'file2.csv', user: 'user2' },
|
||||||
|
];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mapped = mapStateToProps(testState);
|
selectors.grades.bulkManagementHistoryEntries.mockReturnValue(
|
||||||
|
mockHistoryEntries,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
test('bulkManagementHistory from grades.bulkManagementHistoryEntries', () => {
|
|
||||||
|
it('maps bulkManagementHistory from selector', () => {
|
||||||
|
const result = mapStateToProps(mockState);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
mapped.bulkManagementHistory,
|
selectors.grades.bulkManagementHistoryEntries,
|
||||||
).toEqual(selectors.grades.bulkManagementHistoryEntries(testState));
|
).toHaveBeenCalledWith(mockState);
|
||||||
|
expect(result.bulkManagementHistory).toBe(mockHistoryEntries);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,10 +19,8 @@ const ResultsSummary = ({
|
|||||||
text,
|
text,
|
||||||
}) => (
|
}) => (
|
||||||
<Hyperlink
|
<Hyperlink
|
||||||
href={lms.urls.bulkGradesUrlByRow(rowId)}
|
destination={lms.urls.bulkGradesUrlByRow(rowId)}
|
||||||
destination="www.edx.org"
|
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
|
||||||
showLaunchIcon={false}
|
showLaunchIcon={false}
|
||||||
>
|
>
|
||||||
<Icon src={Download} className="d-inline-block" />
|
<Icon src={Download} className="d-inline-block" />
|
||||||
|
|||||||
@@ -1,21 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
|
||||||
|
|
||||||
import { Download } from '@openedx/paragon/icons';
|
|
||||||
|
|
||||||
import lms from 'data/services/lms';
|
import lms from 'data/services/lms';
|
||||||
|
import { renderWithIntl, screen } from '../../testUtilsExtra';
|
||||||
import ResultsSummary from './ResultsSummary';
|
import ResultsSummary from './ResultsSummary';
|
||||||
|
|
||||||
jest.mock('@openedx/paragon', () => ({
|
|
||||||
Hyperlink: () => 'Hyperlink',
|
|
||||||
Icon: () => 'Icon',
|
|
||||||
}));
|
|
||||||
jest.mock('@openedx/paragon/icons', () => ({
|
|
||||||
Download: 'DownloadIcon',
|
|
||||||
}));
|
|
||||||
jest.mock('data/services/lms', () => ({
|
jest.mock('data/services/lms', () => ({
|
||||||
urls: {
|
urls: {
|
||||||
bulkGradesUrlByRow: jest.fn((rowId) => ({ url: { rowId } })),
|
bulkGradesUrlByRow: jest.fn((rowId) => (`www.edx.org/${rowId}`)),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -24,29 +15,21 @@ describe('ResultsSummary component', () => {
|
|||||||
rowId: 42,
|
rowId: 42,
|
||||||
text: 'texty',
|
text: 'texty',
|
||||||
};
|
};
|
||||||
let el;
|
let link;
|
||||||
const assertions = [
|
|
||||||
'safe hyperlink with bulkGradesUrl with course and row id',
|
|
||||||
'download icon',
|
|
||||||
'results text',
|
|
||||||
];
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
el = shallow(<ResultsSummary {...props} />);
|
renderWithIntl(<ResultsSummary {...props} />);
|
||||||
});
|
link = screen.getByRole('link', { name: props.text });
|
||||||
test(`snapshot - ${assertions.join(', ')}`, () => {
|
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
|
||||||
});
|
});
|
||||||
test('Hyperlink has target="_blank" and rel="noopener noreferrer"', () => {
|
test('Hyperlink has target="_blank" and rel="noopener noreferrer"', () => {
|
||||||
expect(el.instance.props.target).toEqual('_blank');
|
expect(link).toHaveAttribute('target', '_blank');
|
||||||
expect(el.instance.props.rel).toEqual('noopener noreferrer');
|
expect(link).toHaveAttribute('rel', 'noopener noreferrer');
|
||||||
});
|
});
|
||||||
test('Hyperlink has href to bulkGradesUrl', () => {
|
test('Hyperlink has href to bulkGradesUrl', () => {
|
||||||
expect(el.instance.props.href).toEqual(lms.urls.bulkGradesUrlByRow(props.rowId));
|
expect(link).toHaveAttribute('href', lms.urls.bulkGradesUrlByRow(props.rowId));
|
||||||
});
|
});
|
||||||
test('displays Download Icon and text', () => {
|
test('displays Download Icon and text', () => {
|
||||||
const icon = el.instance.children[0];
|
expect(link).toHaveTextContent(props.text);
|
||||||
expect(icon.type).toEqual('Icon');
|
const icon = screen.getByRole('img', { hidden: true });
|
||||||
expect(icon.props.src).toEqual(Download);
|
expect(icon).toBeInTheDocument();
|
||||||
expect(el.instance.children[1].el).toEqual(props.text);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`BulkManagementAlerts component no errer, no upload success snapshot - bulkImportError closed, success closed 1`] = `
|
|
||||||
<Fragment>
|
|
||||||
<Alert
|
|
||||||
dismissible={false}
|
|
||||||
show={false}
|
|
||||||
variant="danger"
|
|
||||||
/>
|
|
||||||
<Alert
|
|
||||||
dismissible={false}
|
|
||||||
show={false}
|
|
||||||
variant="success"
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="CSV processing. File uploads may take several minutes to complete."
|
|
||||||
description="Success Dialog message in BulkManagement Tab File Upload Form"
|
|
||||||
id="gradebook.BulkManagementHistoryView.successDialog"
|
|
||||||
/>
|
|
||||||
</Alert>
|
|
||||||
</Fragment>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`BulkManagementAlerts component no errer, no upload success snapshot - danger alert open with bulkImportError, success alert open with messages.successDialog 1`] = `
|
|
||||||
<Fragment>
|
|
||||||
<Alert
|
|
||||||
dismissible={false}
|
|
||||||
show={true}
|
|
||||||
variant="danger"
|
|
||||||
>
|
|
||||||
Oh noooooo
|
|
||||||
</Alert>
|
|
||||||
<Alert
|
|
||||||
dismissible={false}
|
|
||||||
show={true}
|
|
||||||
variant="success"
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="CSV processing. File uploads may take several minutes to complete."
|
|
||||||
description="Success Dialog message in BulkManagement Tab File Upload Form"
|
|
||||||
id="gradebook.BulkManagementHistoryView.successDialog"
|
|
||||||
/>
|
|
||||||
</Alert>
|
|
||||||
</Fragment>
|
|
||||||
`;
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`HistoryTable component snapshot history table data (from bulkManagementHistory.map(this.formatHistoryRow) snapshot: maps resultsSummay to ResultsSummary, wraps filename and user, forwards the rest 1`] = `
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"filename": <span
|
|
||||||
className="wrap-text-in-cell"
|
|
||||||
>
|
|
||||||
blue.png
|
|
||||||
</span>,
|
|
||||||
"resultsSummary": <ResultsSummary
|
|
||||||
courseId="Da Bu Dee"
|
|
||||||
rowId={12}
|
|
||||||
text="Da ba daa"
|
|
||||||
/>,
|
|
||||||
"timeUploaded": "65",
|
|
||||||
"user": <span
|
|
||||||
className="wrap-text-in-cell"
|
|
||||||
>
|
|
||||||
Eifel
|
|
||||||
</span>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename": <span
|
|
||||||
className="wrap-text-in-cell"
|
|
||||||
>
|
|
||||||
allStar.jpg
|
|
||||||
</span>,
|
|
||||||
"resultsSummary": <ResultsSummary
|
|
||||||
courseId="rockstar"
|
|
||||||
rowId={2}
|
|
||||||
text="all that glitters is gold"
|
|
||||||
/>,
|
|
||||||
"timeUploaded": "2000s?",
|
|
||||||
"user": <span
|
|
||||||
className="wrap-text-in-cell"
|
|
||||||
>
|
|
||||||
Smashmouth
|
|
||||||
</span>,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`HistoryTable component snapshot snapshot - loads formatted table 1`] = `
|
|
||||||
<DataTable
|
|
||||||
className="table-striped"
|
|
||||||
columns={
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"Header": "Gradebook",
|
|
||||||
"accessor": "filename",
|
|
||||||
"columnSortable": false,
|
|
||||||
"width": "col-5",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Header": "Download Summary",
|
|
||||||
"accessor": "resultsSummary",
|
|
||||||
"columnSortable": false,
|
|
||||||
"width": "col",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Header": "Who",
|
|
||||||
"accessor": "user",
|
|
||||||
"columnSortable": false,
|
|
||||||
"width": "col-1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Header": "When",
|
|
||||||
"accessor": "timeUploaded",
|
|
||||||
"columnSortable": false,
|
|
||||||
"width": "col",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
data={
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"filename": <span
|
|
||||||
className="wrap-text-in-cell"
|
|
||||||
>
|
|
||||||
blue.png
|
|
||||||
</span>,
|
|
||||||
"resultsSummary": <ResultsSummary
|
|
||||||
courseId="Da Bu Dee"
|
|
||||||
rowId={12}
|
|
||||||
text="Da ba daa"
|
|
||||||
/>,
|
|
||||||
"timeUploaded": "65",
|
|
||||||
"user": <span
|
|
||||||
className="wrap-text-in-cell"
|
|
||||||
>
|
|
||||||
Eifel
|
|
||||||
</span>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename": <span
|
|
||||||
className="wrap-text-in-cell"
|
|
||||||
>
|
|
||||||
allStar.jpg
|
|
||||||
</span>,
|
|
||||||
"resultsSummary": <ResultsSummary
|
|
||||||
courseId="rockstar"
|
|
||||||
rowId={2}
|
|
||||||
text="all that glitters is gold"
|
|
||||||
/>,
|
|
||||||
"timeUploaded": "2000s?",
|
|
||||||
"user": <span
|
|
||||||
className="wrap-text-in-cell"
|
|
||||||
>
|
|
||||||
Smashmouth
|
|
||||||
</span>,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
hasFixedColumnWidths={true}
|
|
||||||
itemCount={2}
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`ResultsSummary component snapshot - safe hyperlink with bulkGradesUrl with course and row id, download icon, results text 1`] = `
|
|
||||||
<Hyperlink
|
|
||||||
destination="www.edx.org"
|
|
||||||
href={
|
|
||||||
{
|
|
||||||
"url": {
|
|
||||||
"rowId": 42,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
showLaunchIcon={false}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
className="d-inline-block"
|
|
||||||
src="DownloadIcon"
|
|
||||||
/>
|
|
||||||
texty
|
|
||||||
</Hyperlink>
|
|
||||||
`;
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`BulkManagementHistoryView component snapshot snapshot - loads heading from messages.BulkManagementHistoryView.heading, <BulkManagementAlerts />, <HistoryTable /> 1`] = `
|
|
||||||
<div
|
|
||||||
className="bulk-management-history-view"
|
|
||||||
>
|
|
||||||
<h4>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Bulk Management History"
|
|
||||||
description="Heading text for BulkManagement History Tab"
|
|
||||||
id="gradebook.BulkManagementHistoryView.heading"
|
|
||||||
/>
|
|
||||||
</h4>
|
|
||||||
<p
|
|
||||||
className="help-text"
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Below is a log of previous grade imports. To download a CSV of your gradebook and import grades for override, return to the Gradebook. Please note, after importing grades, it may take a few seconds to process the override."
|
|
||||||
description="Bulk Management History View help text"
|
|
||||||
id="gradebook.BulkManagementHistoryView"
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
<BulkManagementAlerts />
|
|
||||||
<HistoryTable />
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,46 +1,24 @@
|
|||||||
/* eslint-disable import/no-named-as-default */
|
import { render, initializeMocks, screen } from 'testUtilsExtra';
|
||||||
import React from 'react';
|
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
|
||||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
|
||||||
|
|
||||||
import { BulkManagementHistoryView } from '.';
|
import { BulkManagementHistoryView } from '.';
|
||||||
import BulkManagementAlerts from './BulkManagementAlerts';
|
|
||||||
import HistoryTable from './HistoryTable';
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
jest.mock('./BulkManagementAlerts', () => 'BulkManagementAlerts');
|
jest.mock('./BulkManagementAlerts', () => jest.fn(() => <div>BulkManagementAlerts</div>));
|
||||||
jest.mock('./HistoryTable', () => 'HistoryTable');
|
jest.mock('./HistoryTable', () => jest.fn(() => <div>HistoryTable</div>));
|
||||||
|
|
||||||
|
initializeMocks();
|
||||||
|
|
||||||
describe('BulkManagementHistoryView', () => {
|
describe('BulkManagementHistoryView', () => {
|
||||||
describe('component', () => {
|
describe('component', () => {
|
||||||
let el;
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
el = shallow(<BulkManagementHistoryView />);
|
render(<BulkManagementHistoryView />);
|
||||||
});
|
});
|
||||||
describe('snapshot', () => {
|
describe('render alerts and heading', () => {
|
||||||
const snapshotSegments = [
|
it('heading - h4 loaded from messages', () => {
|
||||||
'heading from messages.BulkManagementHistoryView.heading',
|
expect(screen.getByText(messages.heading.defaultMessage)).toBeInTheDocument();
|
||||||
'<BulkManagementAlerts />',
|
expect(screen.getByText(messages.helpText.defaultMessage)).toBeInTheDocument();
|
||||||
'<HistoryTable />',
|
expect(screen.getByText('BulkManagementAlerts')).toBeInTheDocument();
|
||||||
];
|
expect(screen.getByText('HistoryTable')).toBeInTheDocument();
|
||||||
test(`snapshot - loads ${snapshotSegments.join(', ')}`, () => {
|
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
test('heading - h4 loaded from messages', () => {
|
|
||||||
const heading = el.instance.findByType('h4')[0];
|
|
||||||
const expectedHeading = shallow(
|
|
||||||
<h4>
|
|
||||||
<FormattedMessage {...messages.heading} />
|
|
||||||
</h4>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(heading.el.type).toEqual(expectedHeading.type);
|
|
||||||
expect(heading.el.props).toEqual(expectedHeading.props);
|
|
||||||
});
|
|
||||||
test('heading, then alerts, then upload form, then table', () => {
|
|
||||||
expect(el.instance.children[0].type).toEqual('h4');
|
|
||||||
expect(el.instance.children[2].type).toEqual(BulkManagementAlerts);
|
|
||||||
expect(el.instance.children[3].type).toEqual(HistoryTable);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const messages = defineMessages({
|
|||||||
},
|
},
|
||||||
helpText: {
|
helpText: {
|
||||||
id: 'gradebook.BulkManagementHistoryView',
|
id: 'gradebook.BulkManagementHistoryView',
|
||||||
defaultMessage: 'Below is a log of previous grade imports. To download a CSV of your gradebook and import grades for override, return to the Gradebook. Please note, after importing grades, it may take a few seconds to process the override.',
|
defaultMessage: 'Below is a log of previous grade imports. To download a CSV of your gradebook and import grades for override, return to the Gradebook. Please note, after importing grades, it may take a few seconds to process the override.',
|
||||||
description: 'Bulk Management History View help text',
|
description: 'Bulk Management History View help text',
|
||||||
},
|
},
|
||||||
successDialog: {
|
successDialog: {
|
||||||
|
|||||||
35
src/components/EdxHeader/EdxHeader.test.jsx
Normal file
35
src/components/EdxHeader/EdxHeader.test.jsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
|
||||||
|
import Header from '.';
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform', () => ({
|
||||||
|
...jest.requireActual('@edx/frontend-platform'),
|
||||||
|
getConfig: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Header', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('has edx link with logo url', () => {
|
||||||
|
const url = 'www.ourLogo.url';
|
||||||
|
const baseUrl = 'www.lms.url';
|
||||||
|
getConfig.mockReturnValue({ LOGO_URL: url, LMS_BASE_URL: baseUrl });
|
||||||
|
|
||||||
|
render(
|
||||||
|
<IntlProvider messages={{}} locale="en">
|
||||||
|
<Header />
|
||||||
|
</IntlProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const link = screen.getByRole('link');
|
||||||
|
const logo = screen.getByAltText('edX logo');
|
||||||
|
|
||||||
|
expect(link).toHaveAttribute('href', `${baseUrl}/dashboard`);
|
||||||
|
expect(logo).toHaveAttribute('src', url);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`Header snapshot - has edx link with logo url 1`] = `
|
|
||||||
<div
|
|
||||||
className="mb-3"
|
|
||||||
>
|
|
||||||
<header
|
|
||||||
className="d-flex justify-content-center align-items-center p-3 border-bottom-blue"
|
|
||||||
>
|
|
||||||
<Hyperlink
|
|
||||||
destination="undefined/dashboard"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="edX logo"
|
|
||||||
height="30"
|
|
||||||
src="www.ourLogo.url"
|
|
||||||
width="60"
|
|
||||||
/>
|
|
||||||
</Hyperlink>
|
|
||||||
<div />
|
|
||||||
</header>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
|
||||||
|
|
||||||
import Header from '.';
|
|
||||||
|
|
||||||
jest.mock('@openedx/paragon', () => ({
|
|
||||||
Hyperlink: () => 'Hyperlink',
|
|
||||||
}));
|
|
||||||
jest.mock('@edx/frontend-platform', () => ({
|
|
||||||
getConfig: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('Header', () => {
|
|
||||||
test('snapshot - has edx link with logo url', () => {
|
|
||||||
const url = 'www.ourLogo.url';
|
|
||||||
getConfig.mockReturnValue({ LOGO_URL: url });
|
|
||||||
expect(shallow(<Header />).snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`AssignmentFilter component render snapshot 1`] = `
|
|
||||||
<div
|
|
||||||
className="student-filters"
|
|
||||||
>
|
|
||||||
<SelectGroup
|
|
||||||
disabled={false}
|
|
||||||
id="assignment"
|
|
||||||
label="Assignment"
|
|
||||||
onChange={[MockFunction]}
|
|
||||||
options={
|
|
||||||
[
|
|
||||||
<option
|
|
||||||
value=""
|
|
||||||
>
|
|
||||||
All
|
|
||||||
</option>,
|
|
||||||
<option
|
|
||||||
value="label1"
|
|
||||||
>
|
|
||||||
label1
|
|
||||||
:
|
|
||||||
sLabel1
|
|
||||||
</option>,
|
|
||||||
<option
|
|
||||||
value="label2"
|
|
||||||
>
|
|
||||||
label2
|
|
||||||
:
|
|
||||||
sLabel2
|
|
||||||
</option>,
|
|
||||||
<option
|
|
||||||
value="label3"
|
|
||||||
>
|
|
||||||
label3
|
|
||||||
:
|
|
||||||
sLabel3
|
|
||||||
</option>,
|
|
||||||
<option
|
|
||||||
value="label4"
|
|
||||||
>
|
|
||||||
label4
|
|
||||||
:
|
|
||||||
sLabel4
|
|
||||||
</option>,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
value="test-label"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { render, screen, initializeMocks } from 'testUtilsExtra';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
|
||||||
|
|
||||||
import SelectGroup from '../SelectGroup';
|
|
||||||
import useAssignmentFilterData from './hooks';
|
import useAssignmentFilterData from './hooks';
|
||||||
import AssignmentFilter from '.';
|
import AssignmentFilter from '.';
|
||||||
|
|
||||||
jest.mock('../SelectGroup', () => 'SelectGroup');
|
|
||||||
jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
|
jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
|
||||||
|
|
||||||
const handleChange = jest.fn();
|
const handleChange = jest.fn();
|
||||||
@@ -25,29 +22,16 @@ useAssignmentFilterData.mockReturnValue({
|
|||||||
|
|
||||||
const updateQueryParams = jest.fn();
|
const updateQueryParams = jest.fn();
|
||||||
|
|
||||||
let el;
|
|
||||||
describe('AssignmentFilter component', () => {
|
describe('AssignmentFilter component', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
el = shallow(<AssignmentFilter updateQueryParams={updateQueryParams} />);
|
initializeMocks();
|
||||||
});
|
render(<AssignmentFilter updateQueryParams={updateQueryParams} />);
|
||||||
describe('behavior', () => {
|
|
||||||
it('initializes hooks', () => {
|
|
||||||
expect(useAssignmentFilterData).toHaveBeenCalledWith({ updateQueryParams });
|
|
||||||
expect(useIntl).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
test('snapshot', () => {
|
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
test('filter options', () => {
|
test('filter options', () => {
|
||||||
const { options } = el.instance.findByType(SelectGroup)[0].props;
|
expect(screen.getByRole('combobox', { name: 'Assignment' })).toBeInTheDocument();
|
||||||
expect(options.length).toEqual(5);
|
expect(screen.getAllByRole('option')).toHaveLength(assignmentFilterOptions.length + 1); // +1 for the default option
|
||||||
const testOption = assignmentFilterOptions[0];
|
expect(screen.getAllByRole('option')[assignmentFilterOptions.length]).toHaveTextContent(assignmentFilterOptions[assignmentFilterOptions.length - 1].label);
|
||||||
const optionProps = options[1].props;
|
|
||||||
expect(optionProps.value).toEqual(testOption.label);
|
|
||||||
expect(optionProps.children.join(''))
|
|
||||||
.toEqual(`${testOption.label}: ${testOption.subsectionLabel}`);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`AssignmentFilter component render with selected assignment snapshot 1`] = `
|
|
||||||
<div
|
|
||||||
className="grade-filter-inputs"
|
|
||||||
>
|
|
||||||
<PercentGroup
|
|
||||||
disabled={false}
|
|
||||||
id="assignmentGradeMin"
|
|
||||||
label="Min Grade"
|
|
||||||
onChange={[MockFunction]}
|
|
||||||
value={23}
|
|
||||||
/>
|
|
||||||
<PercentGroup
|
|
||||||
disabled={false}
|
|
||||||
id="assignmentGradeMax"
|
|
||||||
label="Max Grade"
|
|
||||||
onChange={[MockFunction]}
|
|
||||||
value={300}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="grade-filter-action"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
disabled={false}
|
|
||||||
name="assignmentGradeMinMax"
|
|
||||||
type="submit"
|
|
||||||
variant="outline-secondary"
|
|
||||||
>
|
|
||||||
Apply
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`AssignmentFilter component render without selected assignment snapshot 1`] = `
|
|
||||||
<div
|
|
||||||
className="grade-filter-inputs"
|
|
||||||
>
|
|
||||||
<PercentGroup
|
|
||||||
disabled={true}
|
|
||||||
id="assignmentGradeMin"
|
|
||||||
label="Min Grade"
|
|
||||||
onChange={[MockFunction]}
|
|
||||||
value={23}
|
|
||||||
/>
|
|
||||||
<PercentGroup
|
|
||||||
disabled={true}
|
|
||||||
id="assignmentGradeMax"
|
|
||||||
label="Max Grade"
|
|
||||||
onChange={[MockFunction]}
|
|
||||||
value={300}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="grade-filter-action"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
disabled={true}
|
|
||||||
name="assignmentGradeMinMax"
|
|
||||||
type="submit"
|
|
||||||
variant="outline-secondary"
|
|
||||||
>
|
|
||||||
Apply
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
import React from 'react';
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { screen } from '@testing-library/react';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { Button } from '@openedx/paragon';
|
|
||||||
|
|
||||||
import PercentGroup from '../PercentGroup';
|
|
||||||
import useAssignmentGradeFilterData from './hooks';
|
import useAssignmentGradeFilterData from './hooks';
|
||||||
import AssignmentFilter from '.';
|
import AssignmentFilter from '.';
|
||||||
|
import { renderWithIntl } from '../../../testUtilsExtra';
|
||||||
|
|
||||||
jest.mock('../PercentGroup', () => 'PercentGroup');
|
|
||||||
jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
|
jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
|
||||||
|
|
||||||
const hookData = {
|
const hookData = {
|
||||||
handleChange: jest.fn(),
|
handleSubmit: jest.fn(),
|
||||||
handleSetMax: jest.fn(),
|
handleSetMax: jest.fn(),
|
||||||
handleSetMin: jest.fn(),
|
handleSetMin: jest.fn(),
|
||||||
selectedAssignment: 'test-assignment',
|
selectedAssignment: 'test-assignment',
|
||||||
@@ -22,37 +20,39 @@ useAssignmentGradeFilterData.mockReturnValue(hookData);
|
|||||||
|
|
||||||
const updateQueryParams = jest.fn();
|
const updateQueryParams = jest.fn();
|
||||||
|
|
||||||
let el;
|
|
||||||
describe('AssignmentFilter component', () => {
|
describe('AssignmentFilter component', () => {
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
el = shallow(<AssignmentFilter updateQueryParams={updateQueryParams} />);
|
|
||||||
});
|
|
||||||
describe('behavior', () => {
|
describe('behavior', () => {
|
||||||
it('initializes hooks', () => {
|
it('initializes hooks', () => {
|
||||||
|
renderWithIntl(<AssignmentFilter updateQueryParams={updateQueryParams} />);
|
||||||
expect(useAssignmentGradeFilterData).toHaveBeenCalledWith({ updateQueryParams });
|
expect(useAssignmentGradeFilterData).toHaveBeenCalledWith({ updateQueryParams });
|
||||||
expect(useIntl).toHaveBeenCalledWith();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
describe('with selected assignment', () => {
|
describe('with selected assignment', () => {
|
||||||
test('snapshot', () => {
|
beforeEach(() => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
jest.clearAllMocks();
|
||||||
|
renderWithIntl(<AssignmentFilter updateQueryParams={updateQueryParams} />);
|
||||||
});
|
});
|
||||||
it('renders a PercentGroup for both Max and Min filters', () => {
|
it('renders a PercentGroup for both Max and Min filters', async () => {
|
||||||
let { props } = el.instance.findByType(PercentGroup)[0];
|
const user = userEvent.setup();
|
||||||
expect(props.value).toEqual(hookData.assignmentGradeMin);
|
const minGradeInput = screen.getByRole('spinbutton', { name: /Min Grade/i });
|
||||||
expect(props.disabled).toEqual(false);
|
const maxGradeInput = screen.getByRole('spinbutton', { name: /Max Grade/i });
|
||||||
expect(props.onChange).toEqual(hookData.handleSetMin);
|
expect(minGradeInput).toBeInTheDocument();
|
||||||
props = el.instance.findByType(PercentGroup)[1].props;
|
expect(maxGradeInput).toBeInTheDocument();
|
||||||
expect(props.value).toEqual(hookData.assignmentGradeMax);
|
expect(minGradeInput).toBeEnabled();
|
||||||
expect(props.disabled).toEqual(false);
|
expect(maxGradeInput).toBeEnabled();
|
||||||
expect(props.onChange).toEqual(hookData.handleSetMax);
|
await user.type(minGradeInput, '25');
|
||||||
|
expect(hookData.handleSetMin).toHaveBeenCalled();
|
||||||
|
await user.type(maxGradeInput, '50');
|
||||||
|
expect(hookData.handleSetMax).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it('renders a submit button', () => {
|
it('renders a submit button', async () => {
|
||||||
const { props } = el.instance.findByType(Button)[0];
|
const user = userEvent.setup();
|
||||||
expect(props.disabled).toEqual(false);
|
const submitButton = screen.getByRole('button', { name: /Apply/ });
|
||||||
expect(props.onClick).toEqual(hookData.handleSubmit);
|
expect(submitButton).toBeInTheDocument();
|
||||||
|
expect(submitButton).not.toHaveAttribute('disabled');
|
||||||
|
await user.click(submitButton);
|
||||||
|
expect(hookData.handleSubmit).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('without selected assignment', () => {
|
describe('without selected assignment', () => {
|
||||||
@@ -61,16 +61,13 @@ describe('AssignmentFilter component', () => {
|
|||||||
...hookData,
|
...hookData,
|
||||||
selectedAssignment: null,
|
selectedAssignment: null,
|
||||||
});
|
});
|
||||||
el = shallow(<AssignmentFilter updateQueryParams={updateQueryParams} />);
|
renderWithIntl(<AssignmentFilter updateQueryParams={updateQueryParams} />);
|
||||||
});
|
|
||||||
test('snapshot', () => {
|
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
|
||||||
});
|
});
|
||||||
it('disables controls', () => {
|
it('disables controls', () => {
|
||||||
let { props } = el.instance.findByType(PercentGroup)[0];
|
const minGrade = screen.getByRole('spinbutton', { name: /Min Grade/ });
|
||||||
expect(props.disabled).toEqual(true);
|
const maxGrade = screen.getByRole('spinbutton', { name: /Max Grade/ });
|
||||||
props = el.instance.findByType(PercentGroup)[1].props;
|
expect(minGrade).toHaveAttribute('disabled');
|
||||||
expect(props.disabled).toEqual(true);
|
expect(maxGrade).toHaveAttribute('disabled');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`AssignmentFilterType component render snapshot 1`] = `
|
|
||||||
<div
|
|
||||||
className="student-filters"
|
|
||||||
>
|
|
||||||
<SelectGroup
|
|
||||||
disabled={true}
|
|
||||||
id="assignment-types"
|
|
||||||
label="Assignment Types"
|
|
||||||
onChange={[MockFunction]}
|
|
||||||
options={
|
|
||||||
[
|
|
||||||
<option
|
|
||||||
value=""
|
|
||||||
>
|
|
||||||
All
|
|
||||||
</option>,
|
|
||||||
<option
|
|
||||||
value="test-type"
|
|
||||||
>
|
|
||||||
test-type
|
|
||||||
</option>,
|
|
||||||
<option
|
|
||||||
value="type1"
|
|
||||||
>
|
|
||||||
type1
|
|
||||||
</option>,
|
|
||||||
<option
|
|
||||||
value="type2"
|
|
||||||
>
|
|
||||||
type2
|
|
||||||
</option>,
|
|
||||||
<option
|
|
||||||
value="type3"
|
|
||||||
>
|
|
||||||
type3
|
|
||||||
</option>,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
value="test-type"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { screen } from '@testing-library/react';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
|
||||||
|
|
||||||
import SelectGroup from '../SelectGroup';
|
|
||||||
import useAssignmentFilterTypeData from './hooks';
|
import useAssignmentFilterTypeData from './hooks';
|
||||||
import AssignmentFilterType from '.';
|
import AssignmentFilterType from '.';
|
||||||
|
import { renderWithIntl } from '../../../testUtilsExtra';
|
||||||
|
|
||||||
jest.mock('../SelectGroup', () => 'SelectGroup');
|
|
||||||
jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
|
jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
|
||||||
|
|
||||||
const handleChange = jest.fn();
|
const handleChange = jest.fn();
|
||||||
@@ -21,27 +19,15 @@ useAssignmentFilterTypeData.mockReturnValue({
|
|||||||
|
|
||||||
const updateQueryParams = jest.fn();
|
const updateQueryParams = jest.fn();
|
||||||
|
|
||||||
let el;
|
|
||||||
describe('AssignmentFilterType component', () => {
|
describe('AssignmentFilterType component', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
el = shallow(<AssignmentFilterType updateQueryParams={updateQueryParams} />);
|
renderWithIntl(<AssignmentFilterType updateQueryParams={updateQueryParams} />);
|
||||||
});
|
|
||||||
describe('behavior', () => {
|
|
||||||
it('initializes hooks', () => {
|
|
||||||
expect(useAssignmentFilterTypeData).toHaveBeenCalledWith({ updateQueryParams });
|
|
||||||
expect(useIntl).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
test('snapshot', () => {
|
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
test('filter options', () => {
|
test('filter options', () => {
|
||||||
const { options } = el.instance.findByType(SelectGroup)[0].props;
|
const options = screen.getAllByRole('option');
|
||||||
expect(options.length).toEqual(5);
|
expect(options.length).toEqual(5); // 4 types + "All Types"
|
||||||
const optionProps = options[1].props;
|
expect(options[1]).toHaveTextContent(testType);
|
||||||
expect(optionProps.value).toEqual(assignmentTypes[0]);
|
|
||||||
expect(optionProps.children).toEqual(testType);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`CourseFilter component render if disabled snapshot 1`] = `
|
|
||||||
<Fragment>
|
|
||||||
<div
|
|
||||||
className="grade-filter-inputs"
|
|
||||||
>
|
|
||||||
<PercentGroup
|
|
||||||
id="minimum-grade"
|
|
||||||
label="Min Grade"
|
|
||||||
onChange={[MockFunction]}
|
|
||||||
value={23}
|
|
||||||
/>
|
|
||||||
<PercentGroup
|
|
||||||
id="maximum-grade"
|
|
||||||
label="Max Grade"
|
|
||||||
onChange={[MockFunction]}
|
|
||||||
value={300}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="grade-filter-action"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
disabled={true}
|
|
||||||
variant="outline-secondary"
|
|
||||||
>
|
|
||||||
Apply
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Fragment>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`CourseFilter component render with selected assignment snapshot 1`] = `
|
|
||||||
<Fragment>
|
|
||||||
<div
|
|
||||||
className="grade-filter-inputs"
|
|
||||||
>
|
|
||||||
<PercentGroup
|
|
||||||
id="minimum-grade"
|
|
||||||
label="Min Grade"
|
|
||||||
onChange={[MockFunction]}
|
|
||||||
value={23}
|
|
||||||
/>
|
|
||||||
<PercentGroup
|
|
||||||
id="maximum-grade"
|
|
||||||
label="Max Grade"
|
|
||||||
onChange={[MockFunction]}
|
|
||||||
value={300}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="grade-filter-action"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
disabled={false}
|
|
||||||
variant="outline-secondary"
|
|
||||||
>
|
|
||||||
Apply
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Fragment>
|
|
||||||
`;
|
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { screen } from '@testing-library/react';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
|
||||||
import { Button } from '@openedx/paragon';
|
|
||||||
|
|
||||||
import PercentGroup from '../PercentGroup';
|
|
||||||
import useCourseGradeFilterData from './hooks';
|
import useCourseGradeFilterData from './hooks';
|
||||||
import CourseFilter from '.';
|
import CourseFilter from '.';
|
||||||
|
import { renderWithIntl } from '../../../testUtilsExtra';
|
||||||
|
|
||||||
jest.mock('../PercentGroup', () => 'PercentGroup');
|
|
||||||
jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
|
jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
|
||||||
|
|
||||||
const hookData = {
|
const hookData = {
|
||||||
@@ -27,48 +24,37 @@ useCourseGradeFilterData.mockReturnValue(hookData);
|
|||||||
|
|
||||||
const updateQueryParams = jest.fn();
|
const updateQueryParams = jest.fn();
|
||||||
|
|
||||||
let el;
|
|
||||||
describe('CourseFilter component', () => {
|
describe('CourseFilter component', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
el = shallow(<CourseFilter updateQueryParams={updateQueryParams} />);
|
|
||||||
});
|
|
||||||
describe('behavior', () => {
|
|
||||||
it('initializes hooks', () => {
|
|
||||||
expect(useCourseGradeFilterData).toHaveBeenCalledWith({ updateQueryParams });
|
|
||||||
expect(useIntl).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
describe('with selected assignment', () => {
|
describe('with selected assignment', () => {
|
||||||
test('snapshot', () => {
|
beforeEach(() => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
renderWithIntl(<CourseFilter updateQueryParams={updateQueryParams} />);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a PercentGroup for both Max and Min filters', () => {
|
it('renders a PercentGroup for both Max and Min filters', () => {
|
||||||
let { props } = el.instance.findByType(PercentGroup)[0];
|
expect(screen.getByRole('spinbutton', { name: 'Min Grade' })).toHaveValue(hookData.min.value);
|
||||||
expect(props.value).toEqual(hookData.min.value);
|
expect(screen.getByRole('spinbutton', { name: 'Max Grade' })).toHaveValue(hookData.max.value);
|
||||||
expect(props.onChange).toEqual(hookData.min.onChange);
|
|
||||||
props = el.instance.findByType(PercentGroup)[1].props;
|
|
||||||
expect(props.value).toEqual(hookData.max.value);
|
|
||||||
expect(props.onChange).toEqual(hookData.max.onChange);
|
|
||||||
});
|
});
|
||||||
it('renders a submit button', () => {
|
it('renders a submit button', () => {
|
||||||
const { props } = el.instance.findByType(Button)[0];
|
expect(screen.getByRole('button', { name: 'Apply' })).toBeInTheDocument();
|
||||||
expect(props.disabled).toEqual(false);
|
// Expect it to be enabled
|
||||||
expect(props.onClick).toEqual(hookData.handleApplyClick);
|
expect(screen.getByRole('button', { name: 'Apply' })).not.toBeDisabled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('if disabled', () => {
|
describe('if disabled', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
useCourseGradeFilterData.mockReturnValueOnce({ ...hookData, isDisabled: true });
|
useCourseGradeFilterData.mockReturnValueOnce({ ...hookData, isDisabled: true });
|
||||||
el = shallow(<CourseFilter updateQueryParams={updateQueryParams} />);
|
renderWithIntl(<CourseFilter updateQueryParams={updateQueryParams} />);
|
||||||
});
|
|
||||||
test('snapshot', () => {
|
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
|
||||||
});
|
});
|
||||||
it('disables submit', () => {
|
it('disables submit', () => {
|
||||||
const { props } = el.instance.findByType(Button)[0];
|
expect(screen.getByRole('button', { name: 'Apply' })).toBeDisabled();
|
||||||
expect(props.disabled).toEqual(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import { render, screen, initializeMocks } from 'testUtilsExtra';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
|
||||||
|
|
||||||
import PercentGroup from './PercentGroup';
|
import PercentGroup from './PercentGroup';
|
||||||
|
|
||||||
@@ -12,6 +11,7 @@ describe('PercentGroup', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
initializeMocks();
|
||||||
props = {
|
props = {
|
||||||
...props,
|
...props,
|
||||||
onChange: jest.fn().mockName('props.onChange'),
|
onChange: jest.fn().mockName('props.onChange'),
|
||||||
@@ -19,15 +19,17 @@ describe('PercentGroup', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Component', () => {
|
describe('Component', () => {
|
||||||
describe('snapshots', () => {
|
test('is displayed', () => {
|
||||||
test('basic snapshot', () => {
|
render(<PercentGroup {...props} />);
|
||||||
const el = shallow(<PercentGroup {...props} />);
|
expect(screen.getByRole('spinbutton', { name: 'Group Label' })).toBeInTheDocument();
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
expect(screen.getByText('Group Label')).toBeVisible();
|
||||||
});
|
expect(screen.getByText('%')).toBeVisible();
|
||||||
test('disabled', () => {
|
});
|
||||||
const el = shallow(<PercentGroup {...props} disabled />);
|
test('disabled', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
render(<PercentGroup {...props} disabled />);
|
||||||
});
|
expect(screen.getByRole('spinbutton', { name: 'Group Label' })).toBeDisabled();
|
||||||
|
expect(screen.getByText('Group Label')).toBeVisible();
|
||||||
|
expect(screen.getByText('%')).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { render, screen } from '@testing-library/react';
|
||||||
|
|
||||||
import SelectGroup from './SelectGroup';
|
import SelectGroup from './SelectGroup';
|
||||||
|
|
||||||
@@ -24,15 +24,14 @@ describe('SelectGroup', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Component', () => {
|
describe('Component', () => {
|
||||||
describe('snapshots', () => {
|
test('rendered with all options and label', () => {
|
||||||
test('basic snapshot', () => {
|
render(<SelectGroup {...props} />);
|
||||||
const el = shallow(<SelectGroup {...props} />);
|
expect(screen.getAllByRole('option')).toHaveLength(props.options.length);
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
expect(screen.getByLabelText(props.label)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
test('disabled', () => {
|
test('disabled', () => {
|
||||||
const el = shallow(<SelectGroup {...props} disabled />);
|
render(<SelectGroup {...props} disabled />);
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
expect(screen.getByRole('combobox')).toBeDisabled();
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`StudentGroupsFilter component render snapshot 1`] = `
|
|
||||||
<Fragment>
|
|
||||||
<SelectGroup
|
|
||||||
id="Tracks"
|
|
||||||
label="Tracks"
|
|
||||||
onChange={[MockFunction]}
|
|
||||||
options={
|
|
||||||
[
|
|
||||||
<option
|
|
||||||
value="Track-All"
|
|
||||||
>
|
|
||||||
Track-All
|
|
||||||
</option>,
|
|
||||||
<option
|
|
||||||
value="v1"
|
|
||||||
>
|
|
||||||
n1
|
|
||||||
</option>,
|
|
||||||
<option
|
|
||||||
value="v2"
|
|
||||||
>
|
|
||||||
n2
|
|
||||||
</option>,
|
|
||||||
<option
|
|
||||||
value="v3"
|
|
||||||
>
|
|
||||||
n3
|
|
||||||
</option>,
|
|
||||||
<option
|
|
||||||
value="v4"
|
|
||||||
>
|
|
||||||
n4
|
|
||||||
</option>,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
value="test-track"
|
|
||||||
/>
|
|
||||||
<SelectGroup
|
|
||||||
disabled={false}
|
|
||||||
id="Cohorts"
|
|
||||||
label="Cohorts"
|
|
||||||
onChange={[MockFunction]}
|
|
||||||
options={
|
|
||||||
[
|
|
||||||
<option
|
|
||||||
value="Cohort-All"
|
|
||||||
>
|
|
||||||
Cohort-All
|
|
||||||
</option>,
|
|
||||||
<option
|
|
||||||
value="v1"
|
|
||||||
>
|
|
||||||
n1
|
|
||||||
</option>,
|
|
||||||
<option
|
|
||||||
value="v2"
|
|
||||||
>
|
|
||||||
n2
|
|
||||||
</option>,
|
|
||||||
<option
|
|
||||||
value="v3"
|
|
||||||
>
|
|
||||||
n3
|
|
||||||
</option>,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
value="test-cohort"
|
|
||||||
/>
|
|
||||||
</Fragment>
|
|
||||||
`;
|
|
||||||
@@ -1,84 +1,164 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { render, screen, initializeMocks } from 'testUtilsExtra';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
|
||||||
|
|
||||||
import SelectGroup from '../SelectGroup';
|
import SelectGroup from '../SelectGroup';
|
||||||
|
import { StudentGroupsFilter } from './index';
|
||||||
import useStudentGroupsFilterData from './hooks';
|
import useStudentGroupsFilterData from './hooks';
|
||||||
import StudentGroupsFilter from '.';
|
|
||||||
|
|
||||||
jest.mock('../SelectGroup', () => 'SelectGroup');
|
jest.mock('../SelectGroup', () => jest.fn(() => <div data-testid="select-group">SelectGroup</div>));
|
||||||
jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
|
jest.mock('./hooks', () => jest.fn());
|
||||||
|
|
||||||
const props = {
|
initializeMocks();
|
||||||
cohorts: {
|
|
||||||
value: 'test-cohort',
|
describe('StudentGroupsFilter', () => {
|
||||||
|
const mockUpdateQueryParams = jest.fn();
|
||||||
|
|
||||||
|
const mockTracksData = {
|
||||||
|
value: 'test-track-value',
|
||||||
entries: [
|
entries: [
|
||||||
{ value: 'v1', name: 'n1' },
|
{ value: 'track1', name: 'Track 1' },
|
||||||
{ value: 'v2', name: 'n2' },
|
{ value: 'track2', name: 'Track 2' },
|
||||||
{ value: 'v3', name: 'n3' },
|
],
|
||||||
|
handleChange: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockCohortsData = {
|
||||||
|
value: 'test-cohort-value',
|
||||||
|
entries: [
|
||||||
|
{ value: 'cohort1', name: 'Cohort 1' },
|
||||||
|
{ value: 'cohort2', name: 'Cohort 2' },
|
||||||
],
|
],
|
||||||
handleChange: jest.fn(),
|
handleChange: jest.fn(),
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
},
|
};
|
||||||
tracks: {
|
|
||||||
value: 'test-track',
|
|
||||||
entries: [
|
|
||||||
{ value: 'v1', name: 'n1' },
|
|
||||||
{ value: 'v2', name: 'n2' },
|
|
||||||
{ value: 'v3', name: 'n3' },
|
|
||||||
{ value: 'v4', name: 'n4' },
|
|
||||||
],
|
|
||||||
handleChange: jest.fn(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
useStudentGroupsFilterData.mockReturnValue(props);
|
|
||||||
const updateQueryParams = jest.fn();
|
|
||||||
|
|
||||||
let el;
|
beforeEach(() => {
|
||||||
describe('StudentGroupsFilter component', () => {
|
|
||||||
beforeAll(() => {
|
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
el = shallow(<StudentGroupsFilter updateQueryParams={updateQueryParams} />);
|
useStudentGroupsFilterData.mockReturnValue({
|
||||||
});
|
tracks: mockTracksData,
|
||||||
describe('behavior', () => {
|
cohorts: mockCohortsData,
|
||||||
it('initializes hooks', () => {
|
|
||||||
expect(useStudentGroupsFilterData).toHaveBeenCalledWith({ updateQueryParams });
|
|
||||||
expect(useIntl).toHaveBeenCalledWith();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('render', () => {
|
|
||||||
test('snapshot', () => {
|
it('calls useStudentGroupsFilterData hook with updateQueryParams', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
render(<StudentGroupsFilter updateQueryParams={mockUpdateQueryParams} />);
|
||||||
|
|
||||||
|
expect(useStudentGroupsFilterData).toHaveBeenCalledWith({
|
||||||
|
updateQueryParams: mockUpdateQueryParams,
|
||||||
});
|
});
|
||||||
test('track options', () => {
|
});
|
||||||
const {
|
|
||||||
options,
|
it('renders two SelectGroup components', () => {
|
||||||
onChange,
|
render(<StudentGroupsFilter updateQueryParams={mockUpdateQueryParams} />);
|
||||||
value,
|
|
||||||
} = el.instance.findByType(SelectGroup)[0].props;
|
expect(SelectGroup).toHaveBeenCalledTimes(2);
|
||||||
expect(value).toEqual(props.tracks.value);
|
expect(screen.getAllByTestId('select-group')).toHaveLength(2);
|
||||||
expect(onChange).toEqual(props.tracks.handleChange);
|
});
|
||||||
expect(options.length).toEqual(5);
|
|
||||||
const testEntry = props.tracks.entries[0];
|
describe('tracks SelectGroup', () => {
|
||||||
const optionProps = options[1].props;
|
it('renders tracks SelectGroup with correct props', () => {
|
||||||
expect(optionProps.value).toEqual(testEntry.value);
|
render(<StudentGroupsFilter updateQueryParams={mockUpdateQueryParams} />);
|
||||||
expect(optionProps.children).toEqual(testEntry.name);
|
|
||||||
|
const tracksCall = SelectGroup.mock.calls[0][0];
|
||||||
|
expect(tracksCall.id).toBe('Tracks');
|
||||||
|
expect(tracksCall.value).toBe(mockTracksData.value);
|
||||||
|
expect(tracksCall.onChange).toBe(mockTracksData.handleChange);
|
||||||
});
|
});
|
||||||
test('cohort options', () => {
|
|
||||||
const {
|
it('includes trackAll option in tracks SelectGroup', () => {
|
||||||
options,
|
render(<StudentGroupsFilter updateQueryParams={mockUpdateQueryParams} />);
|
||||||
onChange,
|
|
||||||
disabled,
|
const tracksCall = SelectGroup.mock.calls[0][0];
|
||||||
value,
|
const { options } = tracksCall;
|
||||||
} = el.instance.findByType(SelectGroup)[1].props;
|
|
||||||
expect(value).toEqual(props.cohorts.value);
|
expect(options).toHaveLength(3);
|
||||||
expect(disabled).toEqual(false);
|
expect(options[0].props.value).toBeDefined();
|
||||||
expect(onChange).toEqual(props.cohorts.handleChange);
|
expect(options[0].props.children).toBeDefined();
|
||||||
expect(options.length).toEqual(4);
|
});
|
||||||
const testEntry = props.cohorts.entries[0];
|
|
||||||
const optionProps = options[1].props;
|
it('includes track entries in tracks SelectGroup options', () => {
|
||||||
expect(optionProps.value).toEqual(testEntry.value);
|
render(<StudentGroupsFilter updateQueryParams={mockUpdateQueryParams} />);
|
||||||
expect(optionProps.children).toEqual(testEntry.name);
|
|
||||||
|
const tracksCall = SelectGroup.mock.calls[0][0];
|
||||||
|
const { options } = tracksCall;
|
||||||
|
|
||||||
|
expect(options[1].props.value).toBe('track1');
|
||||||
|
expect(options[1].props.children).toBe('Track 1');
|
||||||
|
expect(options[2].props.value).toBe('track2');
|
||||||
|
expect(options[2].props.children).toBe('Track 2');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('cohorts SelectGroup', () => {
|
||||||
|
it('renders cohorts SelectGroup with correct props', () => {
|
||||||
|
render(<StudentGroupsFilter updateQueryParams={mockUpdateQueryParams} />);
|
||||||
|
|
||||||
|
const cohortsCall = SelectGroup.mock.calls[1][0];
|
||||||
|
expect(cohortsCall.id).toBe('Cohorts');
|
||||||
|
expect(cohortsCall.value).toBe(mockCohortsData.value);
|
||||||
|
expect(cohortsCall.onChange).toBe(mockCohortsData.handleChange);
|
||||||
|
expect(cohortsCall.disabled).toBe(mockCohortsData.isDisabled);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes cohortAll option in cohorts SelectGroup', () => {
|
||||||
|
render(<StudentGroupsFilter updateQueryParams={mockUpdateQueryParams} />);
|
||||||
|
|
||||||
|
const cohortsCall = SelectGroup.mock.calls[1][0];
|
||||||
|
const { options } = cohortsCall;
|
||||||
|
|
||||||
|
expect(options).toHaveLength(3);
|
||||||
|
expect(options[0].props.value).toBeDefined();
|
||||||
|
expect(options[0].props.children).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes cohort entries in cohorts SelectGroup options', () => {
|
||||||
|
render(<StudentGroupsFilter updateQueryParams={mockUpdateQueryParams} />);
|
||||||
|
|
||||||
|
const cohortsCall = SelectGroup.mock.calls[1][0];
|
||||||
|
const { options } = cohortsCall;
|
||||||
|
|
||||||
|
expect(options[1].props.value).toBe('cohort1');
|
||||||
|
expect(options[1].props.children).toBe('Cohort 1');
|
||||||
|
expect(options[2].props.value).toBe('cohort2');
|
||||||
|
expect(options[2].props.children).toBe('Cohort 2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes disabled state to cohorts SelectGroup', () => {
|
||||||
|
useStudentGroupsFilterData.mockReturnValue({
|
||||||
|
tracks: mockTracksData,
|
||||||
|
cohorts: { ...mockCohortsData, isDisabled: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<StudentGroupsFilter updateQueryParams={mockUpdateQueryParams} />);
|
||||||
|
|
||||||
|
const cohortsCall = SelectGroup.mock.calls[1][0];
|
||||||
|
expect(cohortsCall.disabled).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with empty entries', () => {
|
||||||
|
it('handles empty tracks entries', () => {
|
||||||
|
useStudentGroupsFilterData.mockReturnValue({
|
||||||
|
tracks: { ...mockTracksData, entries: [] },
|
||||||
|
cohorts: mockCohortsData,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<StudentGroupsFilter updateQueryParams={mockUpdateQueryParams} />);
|
||||||
|
|
||||||
|
const tracksCall = SelectGroup.mock.calls[0][0];
|
||||||
|
expect(tracksCall.options).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles empty cohorts entries', () => {
|
||||||
|
useStudentGroupsFilterData.mockReturnValue({
|
||||||
|
tracks: mockTracksData,
|
||||||
|
cohorts: { ...mockCohortsData, entries: [] },
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<StudentGroupsFilter updateQueryParams={mockUpdateQueryParams} />);
|
||||||
|
|
||||||
|
const cohortsCall = SelectGroup.mock.calls[1][0];
|
||||||
|
expect(cohortsCall.options).toHaveLength(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`PercentGroup Component snapshots basic snapshot 1`] = `
|
|
||||||
<div
|
|
||||||
className="percent-group"
|
|
||||||
>
|
|
||||||
<Form.Group
|
|
||||||
controlId="group id"
|
|
||||||
>
|
|
||||||
<Form.Label>
|
|
||||||
Group Label
|
|
||||||
</Form.Label>
|
|
||||||
<Form.Control
|
|
||||||
disabled={false}
|
|
||||||
max={100}
|
|
||||||
min={0}
|
|
||||||
onChange={[MockFunction props.onChange]}
|
|
||||||
step={1}
|
|
||||||
type="number"
|
|
||||||
value="group VALUE"
|
|
||||||
/>
|
|
||||||
</Form.Group>
|
|
||||||
<span
|
|
||||||
className="input-percent-label"
|
|
||||||
>
|
|
||||||
%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`PercentGroup Component snapshots disabled 1`] = `
|
|
||||||
<div
|
|
||||||
className="percent-group"
|
|
||||||
>
|
|
||||||
<Form.Group
|
|
||||||
controlId="group id"
|
|
||||||
>
|
|
||||||
<Form.Label>
|
|
||||||
Group Label
|
|
||||||
</Form.Label>
|
|
||||||
<Form.Control
|
|
||||||
disabled={true}
|
|
||||||
max={100}
|
|
||||||
min={0}
|
|
||||||
onChange={[MockFunction props.onChange]}
|
|
||||||
step={1}
|
|
||||||
type="number"
|
|
||||||
value="group VALUE"
|
|
||||||
/>
|
|
||||||
</Form.Group>
|
|
||||||
<span
|
|
||||||
className="input-percent-label"
|
|
||||||
>
|
|
||||||
%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`SelectGroup Component snapshots basic snapshot 1`] = `
|
|
||||||
<div
|
|
||||||
className="student-filters"
|
|
||||||
>
|
|
||||||
<Form.Group
|
|
||||||
controlId="group id"
|
|
||||||
>
|
|
||||||
<Form.Label>
|
|
||||||
Group Label
|
|
||||||
</Form.Label>
|
|
||||||
<Form.Control
|
|
||||||
as="select"
|
|
||||||
disabled={false}
|
|
||||||
onChange={[MockFunction props.onChange]}
|
|
||||||
value="group VALUE"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
key="opt1"
|
|
||||||
value="opt1"
|
|
||||||
>
|
|
||||||
Option 1
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
key="opt2"
|
|
||||||
value="opt2"
|
|
||||||
>
|
|
||||||
Option 2
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
key="opt3"
|
|
||||||
value="opt3"
|
|
||||||
>
|
|
||||||
Option 3
|
|
||||||
</option>
|
|
||||||
</Form.Control>
|
|
||||||
</Form.Group>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SelectGroup Component snapshots disabled 1`] = `
|
|
||||||
<div
|
|
||||||
className="student-filters"
|
|
||||||
>
|
|
||||||
<Form.Group
|
|
||||||
controlId="group id"
|
|
||||||
>
|
|
||||||
<Form.Label>
|
|
||||||
Group Label
|
|
||||||
</Form.Label>
|
|
||||||
<Form.Control
|
|
||||||
as="select"
|
|
||||||
disabled={true}
|
|
||||||
onChange={[MockFunction props.onChange]}
|
|
||||||
value="group VALUE"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
key="opt1"
|
|
||||||
value="opt1"
|
|
||||||
>
|
|
||||||
Option 1
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
key="opt2"
|
|
||||||
value="opt2"
|
|
||||||
>
|
|
||||||
Option 2
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
key="opt3"
|
|
||||||
value="opt3"
|
|
||||||
>
|
|
||||||
Option 3
|
|
||||||
</option>
|
|
||||||
</Form.Control>
|
|
||||||
</Form.Group>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`GradebookFilters render snapshot 1`] = `
|
|
||||||
<Fragment>
|
|
||||||
<div
|
|
||||||
className="filter-sidebar-header"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
<Icon
|
|
||||||
className="fa fa-filter"
|
|
||||||
/>
|
|
||||||
</h2>
|
|
||||||
<IconButton
|
|
||||||
alt="Close Filters"
|
|
||||||
aria-label="Close Filters"
|
|
||||||
className="p-1"
|
|
||||||
iconAs="Icon"
|
|
||||||
onClick={[MockFunction hook.closeMenu]}
|
|
||||||
src="Close"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Collapsible
|
|
||||||
className="filter-group mb-3"
|
|
||||||
defaultOpen={true}
|
|
||||||
title="Assignments"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<AssignmentTypeFilter
|
|
||||||
updateQueryParams={[MockFunction]}
|
|
||||||
/>
|
|
||||||
<AssignmentFilter
|
|
||||||
updateQueryParams={[MockFunction]}
|
|
||||||
/>
|
|
||||||
<AssignmentGradeFilter
|
|
||||||
updateQueryParams={[MockFunction]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Collapsible>
|
|
||||||
<Collapsible
|
|
||||||
className="filter-group mb-3"
|
|
||||||
defaultOpen={true}
|
|
||||||
title="Overall Grade"
|
|
||||||
>
|
|
||||||
<CourseGradeFilter
|
|
||||||
updateQueryParams={[MockFunction]}
|
|
||||||
/>
|
|
||||||
</Collapsible>
|
|
||||||
<Collapsible
|
|
||||||
className="filter-group mb-3"
|
|
||||||
defaultOpen={true}
|
|
||||||
title="Student Groups"
|
|
||||||
>
|
|
||||||
<StudentGroupsFilter
|
|
||||||
updateQueryParams={[MockFunction]}
|
|
||||||
/>
|
|
||||||
</Collapsible>
|
|
||||||
<Collapsible
|
|
||||||
className="filter-group mb-3"
|
|
||||||
defaultOpen={true}
|
|
||||||
title="Include Course Team Members"
|
|
||||||
>
|
|
||||||
<Form.Checkbox
|
|
||||||
checked={true}
|
|
||||||
onChange={[MockFunction hook.handleChange]}
|
|
||||||
>
|
|
||||||
Include Course Team Members
|
|
||||||
</Form.Checkbox>
|
|
||||||
</Collapsible>
|
|
||||||
</Fragment>
|
|
||||||
`;
|
|
||||||
@@ -1,82 +1,30 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { render, screen, initializeMocks } from 'testUtilsExtra';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
|
||||||
import { Collapsible } from '@openedx/paragon';
|
|
||||||
|
|
||||||
import { formatMessage } from 'testUtils';
|
|
||||||
|
|
||||||
import AssignmentTypeFilter from './AssignmentTypeFilter';
|
|
||||||
import AssignmentFilter from './AssignmentFilter';
|
|
||||||
import AssignmentGradeFilter from './AssignmentGradeFilter';
|
|
||||||
import CourseGradeFilter from './CourseGradeFilter';
|
|
||||||
import StudentGroupsFilter from './StudentGroupsFilter';
|
|
||||||
import messages from './messages';
|
|
||||||
|
|
||||||
import useGradebookFiltersData from './hooks';
|
|
||||||
import GradebookFilters from '.';
|
import GradebookFilters from '.';
|
||||||
|
|
||||||
jest.mock('./AssignmentTypeFilter', () => 'AssignmentTypeFilter');
|
|
||||||
jest.mock('./AssignmentFilter', () => 'AssignmentFilter');
|
|
||||||
jest.mock('./AssignmentGradeFilter', () => 'AssignmentGradeFilter');
|
|
||||||
jest.mock('./CourseGradeFilter', () => 'CourseGradeFilter');
|
|
||||||
jest.mock('./StudentGroupsFilter', () => 'StudentGroupsFilter');
|
|
||||||
|
|
||||||
jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
|
|
||||||
|
|
||||||
const hookProps = {
|
|
||||||
closeMenu: jest.fn().mockName('hook.closeMenu'),
|
|
||||||
includeCourseTeamMembers: {
|
|
||||||
value: true,
|
|
||||||
handleChange: jest.fn().mockName('hook.handleChange'),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
useGradebookFiltersData.mockReturnValue(hookProps);
|
|
||||||
|
|
||||||
let el;
|
|
||||||
const updateQueryParams = jest.fn();
|
const updateQueryParams = jest.fn();
|
||||||
|
|
||||||
|
initializeMocks();
|
||||||
|
|
||||||
describe('GradebookFilters', () => {
|
describe('GradebookFilters', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
el = shallow(<GradebookFilters updateQueryParams={updateQueryParams} />);
|
render(<GradebookFilters updateQueryParams={updateQueryParams} />);
|
||||||
});
|
});
|
||||||
describe('behavior', () => {
|
describe('All filters render together', () => {
|
||||||
it('initializes hooks', () => {
|
|
||||||
expect(useGradebookFiltersData).toHaveBeenCalledWith({ updateQueryParams });
|
|
||||||
expect(useIntl).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('render', () => {
|
|
||||||
test('snapshot', () => {
|
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
test('Assignment filters', () => {
|
test('Assignment filters', () => {
|
||||||
expect(el.instance.findByType(Collapsible)[0].children[0]).toMatchObject(shallow(
|
expect(screen.getByRole('combobox', { name: 'Assignment Types' })).toBeInTheDocument();
|
||||||
<div>
|
expect(screen.getByRole('combobox', { name: 'Assignment' })).toBeInTheDocument();
|
||||||
<AssignmentTypeFilter updateQueryParams={updateQueryParams} />
|
|
||||||
<AssignmentFilter updateQueryParams={updateQueryParams} />
|
|
||||||
<AssignmentGradeFilter updateQueryParams={updateQueryParams} />
|
|
||||||
</div>,
|
|
||||||
));
|
|
||||||
});
|
});
|
||||||
test('CourseGrade filters', () => {
|
test('CourseGrade filters', () => {
|
||||||
expect(el.instance.findByType(Collapsible)[1].children[0]).toMatchObject(shallow(
|
expect(screen.getByRole('button', { name: 'Overall Grade' })).toBeInTheDocument();
|
||||||
<CourseGradeFilter updateQueryParams={updateQueryParams} />,
|
|
||||||
));
|
|
||||||
});
|
});
|
||||||
test('StudentGroups filters', () => {
|
test('StudentGroups filters', () => {
|
||||||
expect(el.instance.findByType(Collapsible)[2].children[0]).toMatchObject(shallow(
|
expect(screen.getByRole('button', { name: 'Student Groups' })).toBeInTheDocument();
|
||||||
<StudentGroupsFilter updateQueryParams={updateQueryParams} />,
|
|
||||||
));
|
|
||||||
});
|
});
|
||||||
test('includeCourseTeamMembers', () => {
|
test('includeCourseTeamMembers', () => {
|
||||||
const checkbox = el.instance.findByType(Collapsible)[3].children[0];
|
expect(screen.getByRole('button', { name: 'Include Course Team Members' })).toBeInTheDocument();
|
||||||
expect(checkbox.props).toEqual({
|
|
||||||
checked: true,
|
|
||||||
onChange: hookProps.includeCourseTeamMembers.handleChange,
|
|
||||||
});
|
|
||||||
expect(checkbox.children[0].el).toEqual(formatMessage(messages.includeCourseTeamMembers));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
// 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
|
|
||||||
className="text-break"
|
|
||||||
>
|
|
||||||
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
|
|
||||||
className="text-break"
|
|
||||||
>
|
|
||||||
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
|
|
||||||
className="text-break"
|
|
||||||
>
|
|
||||||
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
|
|
||||||
className="text-break"
|
|
||||||
>
|
|
||||||
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>
|
|
||||||
`;
|
|
||||||
@@ -1,77 +1,300 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { render, screen, initializeMocks } from 'testUtilsExtra';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { Button } from '@openedx/paragon';
|
|
||||||
|
|
||||||
import { formatMessage } from 'testUtils';
|
|
||||||
import { instructorDashboardUrl } from 'data/services/lms/urls';
|
import { instructorDashboardUrl } from 'data/services/lms/urls';
|
||||||
|
|
||||||
|
import { GradebookHeader } from './index';
|
||||||
import useGradebookHeaderData from './hooks';
|
import useGradebookHeaderData from './hooks';
|
||||||
import GradebookHeader from '.';
|
import messages from './messages';
|
||||||
|
|
||||||
jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
|
|
||||||
jest.mock('data/services/lms/urls', () => ({
|
jest.mock('data/services/lms/urls', () => ({
|
||||||
instructorDashboardUrl: jest.fn(),
|
instructorDashboardUrl: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
jest.mock('./hooks', () => jest.fn());
|
||||||
|
|
||||||
instructorDashboardUrl.mockReturnValue('test-dashboard-url');
|
initializeMocks();
|
||||||
|
|
||||||
const hookProps = {
|
describe('GradebookHeader', () => {
|
||||||
areGradesFrozen: false,
|
const mockHandleToggleViewClick = jest.fn();
|
||||||
canUserViewGradebook: true,
|
|
||||||
courseId: 'test-course-id',
|
|
||||||
handleToggleViewClick: jest.fn().mockName('hooks.handleToggleViewClick'),
|
|
||||||
showBulkManagement: false,
|
|
||||||
toggleViewMessage: { defaultMessage: 'toggle-view-message' },
|
|
||||||
};
|
|
||||||
useGradebookHeaderData.mockReturnValue(hookProps);
|
|
||||||
|
|
||||||
let el;
|
beforeEach(() => {
|
||||||
describe('GradebookHeader component', () => {
|
jest.clearAllMocks();
|
||||||
beforeAll(() => {
|
instructorDashboardUrl.mockReturnValue('https://example.com/dashboard');
|
||||||
el = shallow(<GradebookHeader />);
|
|
||||||
});
|
});
|
||||||
describe('behavior', () => {
|
|
||||||
it('initializes hooks', () => {
|
describe('basic rendering', () => {
|
||||||
expect(useGradebookHeaderData).toHaveBeenCalledWith();
|
beforeEach(() => {
|
||||||
expect(useIntl).toHaveBeenCalledWith();
|
useGradebookHeaderData.mockReturnValue({
|
||||||
|
areGradesFrozen: false,
|
||||||
|
canUserViewGradebook: true,
|
||||||
|
courseId: 'course-v1:TestU+CS101+2024',
|
||||||
|
handleToggleViewClick: mockHandleToggleViewClick,
|
||||||
|
showBulkManagement: false,
|
||||||
|
toggleViewMessage: messages.toActivityLog,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the main header container', () => {
|
||||||
|
render(<GradebookHeader />);
|
||||||
|
const header = screen.getByText('Gradebook').closest('.gradebook-header');
|
||||||
|
expect(header).toHaveClass('gradebook-header');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders back to dashboard link', () => {
|
||||||
|
render(<GradebookHeader />);
|
||||||
|
const dashboardLink = screen.getByRole('link');
|
||||||
|
expect(dashboardLink).toHaveAttribute(
|
||||||
|
'href',
|
||||||
|
'https://example.com/dashboard',
|
||||||
|
);
|
||||||
|
expect(dashboardLink).toHaveClass('mb-3');
|
||||||
|
expect(dashboardLink).toHaveTextContent('Back to Dashboard');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders gradebook title', () => {
|
||||||
|
render(<GradebookHeader />);
|
||||||
|
const title = screen.getByRole('heading', { level: 1 });
|
||||||
|
expect(title).toHaveTextContent('Gradebook');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders course ID subtitle', () => {
|
||||||
|
render(<GradebookHeader />);
|
||||||
|
const subtitle = screen.getByRole('heading', { level: 2 });
|
||||||
|
expect(subtitle).toHaveTextContent('course-v1:TestU+CS101+2024');
|
||||||
|
expect(subtitle).toHaveClass('text-break');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders subtitle row with correct classes', () => {
|
||||||
|
render(<GradebookHeader />);
|
||||||
|
const subtitleRow = screen.getByRole('heading', {
|
||||||
|
level: 2,
|
||||||
|
}).parentElement;
|
||||||
|
expect(subtitleRow).toHaveClass(
|
||||||
|
'subtitle-row',
|
||||||
|
'd-flex',
|
||||||
|
'justify-content-between',
|
||||||
|
'align-items-center',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls instructorDashboardUrl to get dashboard URL', () => {
|
||||||
|
render(<GradebookHeader />);
|
||||||
|
expect(instructorDashboardUrl).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls useGradebookHeaderData hook', () => {
|
||||||
|
render(<GradebookHeader />);
|
||||||
|
expect(useGradebookHeaderData).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('render', () => {
|
|
||||||
describe('default view', () => {
|
describe('bulk management toggle button', () => {
|
||||||
test('shapshot', () => {
|
describe('when showBulkManagement is true', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
beforeEach(() => {
|
||||||
|
useGradebookHeaderData.mockReturnValue({
|
||||||
|
areGradesFrozen: false,
|
||||||
|
canUserViewGradebook: true,
|
||||||
|
courseId: 'course-v1:TestU+CS101+2024',
|
||||||
|
handleToggleViewClick: mockHandleToggleViewClick,
|
||||||
|
showBulkManagement: true,
|
||||||
|
toggleViewMessage: messages.toActivityLog,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders toggle view button', () => {
|
||||||
|
render(<GradebookHeader />);
|
||||||
|
expect(screen.getByRole('button')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays correct button text from toggleViewMessage', () => {
|
||||||
|
render(<GradebookHeader />);
|
||||||
|
const toggleButton = screen.getByRole('button');
|
||||||
|
expect(toggleButton).toHaveTextContent('View Bulk Management History');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls handleToggleViewClick when button is clicked', async () => {
|
||||||
|
render(<GradebookHeader />);
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const toggleButton = screen.getByRole('button');
|
||||||
|
|
||||||
|
await user.click(toggleButton);
|
||||||
|
expect(mockHandleToggleViewClick).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays correct message from toggleViewMessage', () => {
|
||||||
|
useGradebookHeaderData.mockReturnValue({
|
||||||
|
areGradesFrozen: false,
|
||||||
|
canUserViewGradebook: true,
|
||||||
|
courseId: 'course-v1:TestU+CS101+2024',
|
||||||
|
handleToggleViewClick: mockHandleToggleViewClick,
|
||||||
|
showBulkManagement: true,
|
||||||
|
toggleViewMessage: messages.toGradesView,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<GradebookHeader />);
|
||||||
|
const toggleButton = screen.getByRole('button');
|
||||||
|
expect(toggleButton).toHaveTextContent('Return to Gradebook');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('show bulk management', () => {
|
|
||||||
|
describe('when showBulkManagement is false', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
useGradebookHeaderData.mockReturnValueOnce({ ...hookProps, showBulkManagement: true });
|
useGradebookHeaderData.mockReturnValue({
|
||||||
el = shallow(<GradebookHeader />);
|
areGradesFrozen: false,
|
||||||
|
canUserViewGradebook: true,
|
||||||
|
courseId: 'course-v1:TestU+CS101+2024',
|
||||||
|
handleToggleViewClick: mockHandleToggleViewClick,
|
||||||
|
showBulkManagement: false,
|
||||||
|
toggleViewMessage: messages.toActivityLog,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
test('snapshot: show toggle view message button with handleToggleViewClick method', () => {
|
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
it('does not render toggle view button', () => {
|
||||||
const { onClick } = el.instance.findByType(Button)[0].props;
|
render(<GradebookHeader />);
|
||||||
expect(onClick).toEqual(hookProps.handleToggleViewClick);
|
expect(screen.queryByRole('button')).not.toBeInTheDocument();
|
||||||
expect(el.instance.findByType(Button)[0].children[0].el).toEqual(formatMessage(hookProps.toggleViewMessage));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('frozen grades', () => {
|
});
|
||||||
|
|
||||||
|
describe('frozen grades warning', () => {
|
||||||
|
describe('when areGradesFrozen is true', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
useGradebookHeaderData.mockReturnValueOnce({ ...hookProps, areGradesFrozen: true });
|
useGradebookHeaderData.mockReturnValue({
|
||||||
el = shallow(<GradebookHeader />);
|
areGradesFrozen: true,
|
||||||
|
canUserViewGradebook: true,
|
||||||
|
courseId: 'course-v1:TestU+CS101+2024',
|
||||||
|
handleToggleViewClick: mockHandleToggleViewClick,
|
||||||
|
showBulkManagement: false,
|
||||||
|
toggleViewMessage: messages.toActivityLog,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
test('snapshot: show frozen warning', () => {
|
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
it('renders frozen warning alert', () => {
|
||||||
|
render(<GradebookHeader />);
|
||||||
|
const alert = screen.getByRole('alert');
|
||||||
|
expect(alert).toHaveClass('alert', 'alert-warning');
|
||||||
|
expect(alert).toHaveTextContent(
|
||||||
|
'The grades for this course are now frozen. Editing of grades is no longer allowed.',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('user cannot view gradebook', () => {
|
|
||||||
|
describe('when areGradesFrozen is false', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
useGradebookHeaderData.mockReturnValueOnce({ ...hookProps, canUserViewGradebook: false });
|
useGradebookHeaderData.mockReturnValue({
|
||||||
el = shallow(<GradebookHeader />);
|
areGradesFrozen: false,
|
||||||
|
canUserViewGradebook: true,
|
||||||
|
courseId: 'course-v1:TestU+CS101+2024',
|
||||||
|
handleToggleViewClick: mockHandleToggleViewClick,
|
||||||
|
showBulkManagement: false,
|
||||||
|
toggleViewMessage: messages.toActivityLog,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
test('snapshot: show unauthorized warning', () => {
|
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
it('does not render frozen warning alert', () => {
|
||||||
|
render(<GradebookHeader />);
|
||||||
|
expect(
|
||||||
|
screen.queryByText(
|
||||||
|
'The grades for this course are now frozen. Editing of grades is no longer allowed.',
|
||||||
|
),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('unauthorized warning', () => {
|
||||||
|
describe('when canUserViewGradebook is false', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
useGradebookHeaderData.mockReturnValue({
|
||||||
|
areGradesFrozen: false,
|
||||||
|
canUserViewGradebook: false,
|
||||||
|
courseId: 'course-v1:TestU+CS101+2024',
|
||||||
|
handleToggleViewClick: mockHandleToggleViewClick,
|
||||||
|
showBulkManagement: false,
|
||||||
|
toggleViewMessage: messages.toActivityLog,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders unauthorized warning alert', () => {
|
||||||
|
render(<GradebookHeader />);
|
||||||
|
const alert = screen.getByRole('alert');
|
||||||
|
expect(alert).toHaveClass('alert', 'alert-warning');
|
||||||
|
expect(alert).toHaveTextContent(
|
||||||
|
'You are not authorized to view the gradebook for this course.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when canUserViewGradebook is true', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
useGradebookHeaderData.mockReturnValue({
|
||||||
|
areGradesFrozen: false,
|
||||||
|
canUserViewGradebook: true,
|
||||||
|
courseId: 'course-v1:TestU+CS101+2024',
|
||||||
|
handleToggleViewClick: mockHandleToggleViewClick,
|
||||||
|
showBulkManagement: false,
|
||||||
|
toggleViewMessage: messages.toActivityLog,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render unauthorized warning alert', () => {
|
||||||
|
render(<GradebookHeader />);
|
||||||
|
expect(
|
||||||
|
screen.queryByText(
|
||||||
|
'You are not authorized to view the gradebook for this course.',
|
||||||
|
),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('multiple warnings', () => {
|
||||||
|
it('renders both frozen and unauthorized warnings when both conditions are true', () => {
|
||||||
|
useGradebookHeaderData.mockReturnValue({
|
||||||
|
areGradesFrozen: true,
|
||||||
|
canUserViewGradebook: false,
|
||||||
|
courseId: 'course-v1:TestU+CS101+2024',
|
||||||
|
handleToggleViewClick: mockHandleToggleViewClick,
|
||||||
|
showBulkManagement: false,
|
||||||
|
toggleViewMessage: messages.toActivityLog,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<GradebookHeader />);
|
||||||
|
const alerts = screen.getAllByRole('alert');
|
||||||
|
expect(alerts).toHaveLength(2);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
'The grades for this course are now frozen. Editing of grades is no longer allowed.',
|
||||||
|
),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
'You are not authorized to view the gradebook for this course.',
|
||||||
|
),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('complete integration', () => {
|
||||||
|
it('renders all elements when showBulkManagement is true', () => {
|
||||||
|
useGradebookHeaderData.mockReturnValue({
|
||||||
|
areGradesFrozen: false,
|
||||||
|
canUserViewGradebook: true,
|
||||||
|
courseId: 'course-v1:TestU+CS101+2024',
|
||||||
|
handleToggleViewClick: mockHandleToggleViewClick,
|
||||||
|
showBulkManagement: true,
|
||||||
|
toggleViewMessage: messages.toActivityLog,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<GradebookHeader />);
|
||||||
|
|
||||||
|
expect(screen.getByRole('link')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('heading', { level: 2 })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`BulkManagementControls render snapshot - show - network and import buttons 1`] = `
|
|
||||||
<div
|
|
||||||
className="d-flex"
|
|
||||||
>
|
|
||||||
<NetworkButton
|
|
||||||
label={
|
|
||||||
{
|
|
||||||
"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>
|
|
||||||
`;
|
|
||||||
@@ -1,32 +1,160 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { render, screen, initializeMocks } from 'testUtilsExtra';
|
||||||
|
|
||||||
|
import NetworkButton from 'components/NetworkButton';
|
||||||
|
import ImportGradesButton from '../ImportGradesButton';
|
||||||
|
|
||||||
|
import { BulkManagementControls } from './index';
|
||||||
import useBulkManagementControlsData from './hooks';
|
import useBulkManagementControlsData from './hooks';
|
||||||
import BulkManagementControls from '.';
|
import messages from './messages';
|
||||||
|
|
||||||
jest.mock('../ImportGradesButton', () => 'ImportGradesButton');
|
|
||||||
jest.mock('components/NetworkButton', () => 'NetworkButton');
|
|
||||||
|
|
||||||
|
jest.mock('components/NetworkButton', () => jest.fn(() => <div data-testid="network-button">NetworkButton</div>));
|
||||||
|
jest.mock('../ImportGradesButton', () => jest.fn(() => (
|
||||||
|
<div data-testid="import-grades-button">ImportGradesButton</div>
|
||||||
|
)));
|
||||||
jest.mock('./hooks', () => jest.fn());
|
jest.mock('./hooks', () => jest.fn());
|
||||||
|
|
||||||
const hookProps = {
|
initializeMocks();
|
||||||
show: true,
|
|
||||||
handleClickExportGrades: jest.fn(),
|
|
||||||
};
|
|
||||||
useBulkManagementControlsData.mockReturnValue(hookProps);
|
|
||||||
|
|
||||||
describe('BulkManagementControls', () => {
|
describe('BulkManagementControls', () => {
|
||||||
describe('behavior', () => {
|
const mockHandleClickExportGrades = jest.fn();
|
||||||
shallow(<BulkManagementControls />);
|
|
||||||
expect(useBulkManagementControlsData).toHaveBeenCalledWith();
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
describe('render', () => {
|
|
||||||
test('snapshot - show - network and import buttons', () => {
|
describe('when show is false', () => {
|
||||||
expect(shallow(<BulkManagementControls />).snapshot).toMatchSnapshot();
|
beforeEach(() => {
|
||||||
|
useBulkManagementControlsData.mockReturnValue({
|
||||||
|
show: false,
|
||||||
|
handleClickExportGrades: mockHandleClickExportGrades,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
test('snapshot - empty if show is not truthy', () => {
|
|
||||||
useBulkManagementControlsData.mockReturnValueOnce({ ...hookProps, show: false });
|
it('renders nothing when show is false', () => {
|
||||||
expect(shallow(<BulkManagementControls />).isEmptyRender()).toEqual(true);
|
render(<BulkManagementControls />);
|
||||||
|
expect(screen.queryByTestId('network-button')).not.toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.queryByTestId('import-grades-button'),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render NetworkButton when show is false', () => {
|
||||||
|
render(<BulkManagementControls />);
|
||||||
|
expect(NetworkButton).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render ImportGradesButton when show is false', () => {
|
||||||
|
render(<BulkManagementControls />);
|
||||||
|
expect(ImportGradesButton).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when show is true', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
useBulkManagementControlsData.mockReturnValue({
|
||||||
|
show: true,
|
||||||
|
handleClickExportGrades: mockHandleClickExportGrades,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the container div with correct class when show is true', () => {
|
||||||
|
render(<BulkManagementControls />);
|
||||||
|
const containerDiv = screen.getByTestId('network-button').parentElement;
|
||||||
|
expect(containerDiv).toHaveClass('d-flex');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders NetworkButton with correct props', () => {
|
||||||
|
render(<BulkManagementControls />);
|
||||||
|
|
||||||
|
expect(NetworkButton).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
label: messages.downloadGradesBtn,
|
||||||
|
onClick: mockHandleClickExportGrades,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('network-button')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders ImportGradesButton', () => {
|
||||||
|
render(<BulkManagementControls />);
|
||||||
|
|
||||||
|
expect(ImportGradesButton).toHaveBeenCalledWith({}, {});
|
||||||
|
expect(screen.getByTestId('import-grades-button')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls handleClickExportGrades when NetworkButton is clicked', () => {
|
||||||
|
render(<BulkManagementControls />);
|
||||||
|
|
||||||
|
const networkButtonCall = NetworkButton.mock.calls[0][0];
|
||||||
|
const { onClick } = networkButtonCall;
|
||||||
|
|
||||||
|
onClick();
|
||||||
|
expect(mockHandleClickExportGrades).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes correct label to NetworkButton', () => {
|
||||||
|
render(<BulkManagementControls />);
|
||||||
|
|
||||||
|
const networkButtonCall = NetworkButton.mock.calls[0][0];
|
||||||
|
expect(networkButtonCall.label).toBe(messages.downloadGradesBtn);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders both buttons in the correct order', () => {
|
||||||
|
render(<BulkManagementControls />);
|
||||||
|
|
||||||
|
expect(NetworkButton).toHaveBeenCalled();
|
||||||
|
expect(ImportGradesButton).toHaveBeenCalled();
|
||||||
|
|
||||||
|
const networkButton = screen.getByTestId('network-button');
|
||||||
|
const importButton = screen.getByTestId('import-grades-button');
|
||||||
|
|
||||||
|
expect(networkButton).toBeInTheDocument();
|
||||||
|
expect(importButton).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hook integration', () => {
|
||||||
|
it('calls useBulkManagementControlsData hook', () => {
|
||||||
|
useBulkManagementControlsData.mockReturnValue({
|
||||||
|
show: true,
|
||||||
|
handleClickExportGrades: mockHandleClickExportGrades,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<BulkManagementControls />);
|
||||||
|
expect(useBulkManagementControlsData).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses the show value from hook to determine rendering', () => {
|
||||||
|
useBulkManagementControlsData.mockReturnValue({
|
||||||
|
show: false,
|
||||||
|
handleClickExportGrades: mockHandleClickExportGrades,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<BulkManagementControls />);
|
||||||
|
expect(screen.queryByTestId('network-button')).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
useBulkManagementControlsData.mockReturnValue({
|
||||||
|
show: true,
|
||||||
|
handleClickExportGrades: mockHandleClickExportGrades,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<BulkManagementControls />);
|
||||||
|
expect(screen.getByTestId('network-button')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes handleClickExportGrades from hook to NetworkButton', () => {
|
||||||
|
const customHandler = jest.fn();
|
||||||
|
useBulkManagementControlsData.mockReturnValue({
|
||||||
|
show: true,
|
||||||
|
handleClickExportGrades: customHandler,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<BulkManagementControls />);
|
||||||
|
|
||||||
|
const networkButtonCall = NetworkButton.mock.calls[0][0];
|
||||||
|
expect(networkButtonCall.onClick).toBe(customHandler);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,17 +1,100 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { render, screen, initializeMocks } from 'testUtilsExtra';
|
||||||
|
|
||||||
import HistoryHeader from './HistoryHeader';
|
import HistoryHeader from './HistoryHeader';
|
||||||
|
|
||||||
|
initializeMocks();
|
||||||
|
|
||||||
describe('HistoryHeader', () => {
|
describe('HistoryHeader', () => {
|
||||||
const props = {
|
const defaultProps = {
|
||||||
id: 'water',
|
id: 'test-id',
|
||||||
label: 'Brita',
|
label: 'Test Label',
|
||||||
value: 'hydration',
|
value: 'Test Value',
|
||||||
};
|
};
|
||||||
describe('Component', () => {
|
|
||||||
test('snapshot', () => {
|
it('renders header with label and value', () => {
|
||||||
expect(shallow(<HistoryHeader {...props} />).snapshot).toMatchSnapshot();
|
render(<HistoryHeader {...defaultProps} />);
|
||||||
});
|
|
||||||
|
expect(screen.getByText('Test Label:')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Test Value')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders header element with correct classes', () => {
|
||||||
|
render(<HistoryHeader {...defaultProps} />);
|
||||||
|
|
||||||
|
const headerElement = screen.getByText('Test Label:');
|
||||||
|
expect(headerElement).toHaveClass('grade-history-header');
|
||||||
|
expect(headerElement).toHaveClass('grade-history-test-id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with string value', () => {
|
||||||
|
const props = {
|
||||||
|
...defaultProps,
|
||||||
|
value: 'String Value',
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<HistoryHeader {...props} />);
|
||||||
|
expect(screen.getByText('String Value')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with number value', () => {
|
||||||
|
const props = {
|
||||||
|
...defaultProps,
|
||||||
|
value: 85,
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<HistoryHeader {...props} />);
|
||||||
|
expect(screen.getByText('85')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with null value (default prop)', () => {
|
||||||
|
const props = {
|
||||||
|
id: 'test-id',
|
||||||
|
label: 'Test Label',
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<HistoryHeader {...props} />);
|
||||||
|
expect(screen.getByText('Test Label:')).toBeInTheDocument();
|
||||||
|
|
||||||
|
const valueDiv = screen.getByText('Test Label:').nextSibling;
|
||||||
|
expect(valueDiv).toBeInTheDocument();
|
||||||
|
expect(valueDiv).toBeEmptyDOMElement();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with React node as label', () => {
|
||||||
|
const props = {
|
||||||
|
...defaultProps,
|
||||||
|
label: <strong>Bold Label</strong>,
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<HistoryHeader {...props} />);
|
||||||
|
const strongElement = screen.getByText('Bold Label');
|
||||||
|
expect(strongElement.tagName).toBe('STRONG');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates correct class name based on id', () => {
|
||||||
|
const props = {
|
||||||
|
...defaultProps,
|
||||||
|
id: 'assignment-name',
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<HistoryHeader {...props} />);
|
||||||
|
const headerElement = screen.getByText('Test Label:');
|
||||||
|
expect(headerElement).toHaveClass('grade-history-assignment-name');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders container structure correctly', () => {
|
||||||
|
render(<HistoryHeader {...defaultProps} />);
|
||||||
|
|
||||||
|
const headerElement = screen.getByText('Test Label:');
|
||||||
|
const valueElement = screen.getByText('Test Value');
|
||||||
|
|
||||||
|
expect(headerElement).toBeInTheDocument();
|
||||||
|
expect(valueElement).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(headerElement).toHaveClass(
|
||||||
|
'grade-history-header',
|
||||||
|
'grade-history-test-id',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,16 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
|
||||||
import { selectors } from 'data/redux/hooks';
|
import { selectors } from 'data/redux/hooks';
|
||||||
|
|
||||||
import { formatMessage } from 'testUtils';
|
import { render, screen, initializeMocks } from 'testUtilsExtra';
|
||||||
|
import ModalHeaders from './ModalHeaders';
|
||||||
import HistoryHeader from './HistoryHeader';
|
|
||||||
import ModalHeaders, { HistoryKeys } from './ModalHeaders';
|
|
||||||
import messages from './messages';
|
|
||||||
|
|
||||||
jest.mock('./HistoryHeader', () => 'HistoryHeader');
|
|
||||||
|
|
||||||
jest.mock('data/redux/hooks', () => ({
|
jest.mock('data/redux/hooks', () => ({
|
||||||
selectors: {
|
selectors: {
|
||||||
@@ -29,57 +21,25 @@ const gradeData = {
|
|||||||
gradeOriginalEarnedGraded: 'test-original-grade',
|
gradeOriginalEarnedGraded: 'test-original-grade',
|
||||||
};
|
};
|
||||||
selectors.grades.useGradeData.mockReturnValue(gradeData);
|
selectors.grades.useGradeData.mockReturnValue(gradeData);
|
||||||
|
initializeMocks();
|
||||||
|
|
||||||
let el;
|
|
||||||
describe('ModalHeaders', () => {
|
describe('ModalHeaders', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
el = shallow(<ModalHeaders />);
|
render(<ModalHeaders />);
|
||||||
});
|
|
||||||
describe('behavior', () => {
|
|
||||||
it('initializes intl', () => {
|
|
||||||
expect(useIntl).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
it('initializes redux hooks', () => {
|
|
||||||
expect(selectors.app.useModalData).toHaveBeenCalled();
|
|
||||||
expect(selectors.grades.useGradeData).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
test('snapshot', () => {
|
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
test('assignment header', () => {
|
test('assignment header', () => {
|
||||||
const headerProps = el.instance.findByType(HistoryHeader)[0].props;
|
expect(screen.getByText(modalData.assignmentName)).toBeInTheDocument();
|
||||||
expect(headerProps).toMatchObject({
|
|
||||||
id: HistoryKeys.assignment,
|
|
||||||
label: formatMessage(messages.assignmentHeader),
|
|
||||||
value: modalData.assignmentName,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
test('student header', () => {
|
test('student header', () => {
|
||||||
const headerProps = el.instance.findByType(HistoryHeader)[1].props;
|
expect(screen.getByText(modalData.updateUserName)).toBeInTheDocument();
|
||||||
expect(headerProps).toMatchObject({
|
|
||||||
id: HistoryKeys.student,
|
|
||||||
label: formatMessage(messages.studentHeader),
|
|
||||||
value: modalData.updateUserName,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
test('originalGrade header', () => {
|
test('originalGrade header', () => {
|
||||||
const headerProps = el.instance.findByType(HistoryHeader)[2].props;
|
expect(screen.getByText(gradeData.gradeOriginalEarnedGraded)).toBeInTheDocument();
|
||||||
expect(headerProps).toMatchObject({
|
|
||||||
id: HistoryKeys.originalGrade,
|
|
||||||
label: formatMessage(messages.originalGradeHeader),
|
|
||||||
value: gradeData.gradeOriginalEarnedGraded,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
test('currentGrade header', () => {
|
test('currentGrade header', () => {
|
||||||
const headerProps = el.instance.findByType(HistoryHeader)[3].props;
|
expect(screen.getByText(gradeData.gradeOverrideCurrentEarnedGradedOverride)).toBeInTheDocument();
|
||||||
expect(headerProps).toMatchObject({
|
|
||||||
id: HistoryKeys.currentGrade,
|
|
||||||
label: formatMessage(messages.currentGradeHeader),
|
|
||||||
value: gradeData.gradeOverrideCurrentEarnedGradedOverride,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`AdjustedGradeInput component render snapshot 1`] = `
|
|
||||||
<span>
|
|
||||||
<Form.Control
|
|
||||||
name="adjustedGradeValue"
|
|
||||||
onChange={[MockFunction hook.onChange]}
|
|
||||||
type="text"
|
|
||||||
value="test-value"
|
|
||||||
/>
|
|
||||||
some-hint-text
|
|
||||||
</span>
|
|
||||||
`;
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { render, screen } from '@testing-library/react';
|
||||||
|
|
||||||
import { Form } from '@openedx/paragon';
|
|
||||||
|
|
||||||
import useAdjustedGradeInputData from './hooks';
|
import useAdjustedGradeInputData from './hooks';
|
||||||
import AdjustedGradeInput from '.';
|
import AdjustedGradeInput from '.';
|
||||||
@@ -15,24 +13,17 @@ const hookProps = {
|
|||||||
};
|
};
|
||||||
useAdjustedGradeInputData.mockReturnValue(hookProps);
|
useAdjustedGradeInputData.mockReturnValue(hookProps);
|
||||||
|
|
||||||
let el;
|
|
||||||
describe('AdjustedGradeInput component', () => {
|
describe('AdjustedGradeInput component', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
el = shallow(<AdjustedGradeInput />);
|
render(<AdjustedGradeInput />);
|
||||||
});
|
|
||||||
describe('behavior', () => {
|
|
||||||
it('initializes hook data', () => {
|
|
||||||
expect(useAdjustedGradeInputData).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
test('snapshot', () => {
|
test('renders input with correct props', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
const input = screen.getByRole('textbox');
|
||||||
const control = el.instance.findByType(Form.Control)[0];
|
expect(input).toBeInTheDocument();
|
||||||
expect(control.props.value).toEqual(hookProps.value);
|
expect(input).toHaveValue(hookProps.value);
|
||||||
expect(control.props.onChange).toEqual(hookProps.onChange);
|
expect(screen.getByText(hookProps.hintText)).toBeInTheDocument();
|
||||||
expect(el.instance.children[1].el).toContain(hookProps.hintText);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`ReasonInput component render snapshot 1`] = `
|
|
||||||
<Form.Control
|
|
||||||
data-testid="reason-input-control"
|
|
||||||
name="reasonForChange"
|
|
||||||
onChange={[MockFunction hook.onChange]}
|
|
||||||
type="text"
|
|
||||||
value="test-value"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
@@ -16,6 +16,12 @@ jest.mock('data/redux/hooks', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('react', () => ({
|
||||||
|
...jest.requireActual('react'),
|
||||||
|
useRef: jest.fn((val) => ({ current: val, useRef: true })),
|
||||||
|
useEffect: jest.fn((cb, prereqs) => ({ useEffect: { cb, prereqs } })),
|
||||||
|
}));
|
||||||
|
|
||||||
const modalData = { reasonForChange: 'test-reason-for-change' };
|
const modalData = { reasonForChange: 'test-reason-for-change' };
|
||||||
const setModalState = jest.fn();
|
const setModalState = jest.fn();
|
||||||
selectors.app.useModalData.mockReturnValue(modalData);
|
selectors.app.useModalData.mockReturnValue(modalData);
|
||||||
@@ -25,6 +31,7 @@ const ref = { current: { focus: jest.fn() }, useRef: true };
|
|||||||
React.useRef.mockReturnValue(ref);
|
React.useRef.mockReturnValue(ref);
|
||||||
|
|
||||||
let out;
|
let out;
|
||||||
|
|
||||||
describe('useReasonInputData hook', () => {
|
describe('useReasonInputData hook', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import React from 'react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
|
||||||
|
|
||||||
import { Form } from '@openedx/paragon';
|
|
||||||
|
|
||||||
import useReasonInputData from './hooks';
|
import useReasonInputData from './hooks';
|
||||||
import ReasonInput from '.';
|
import ReasonInput from '.';
|
||||||
@@ -9,29 +6,26 @@ import ReasonInput from '.';
|
|||||||
jest.mock('./hooks', () => jest.fn());
|
jest.mock('./hooks', () => jest.fn());
|
||||||
|
|
||||||
const hookProps = {
|
const hookProps = {
|
||||||
ref: 'reason-input-ref',
|
ref: jest.fn().mockName('hook.ref'),
|
||||||
onChange: jest.fn().mockName('hook.onChange'),
|
onChange: jest.fn().mockName('hook.onChange'),
|
||||||
value: 'test-value',
|
value: 'test-value',
|
||||||
};
|
};
|
||||||
useReasonInputData.mockReturnValue(hookProps);
|
useReasonInputData.mockReturnValue(hookProps);
|
||||||
|
|
||||||
let el;
|
|
||||||
describe('ReasonInput component', () => {
|
describe('ReasonInput component', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
el = shallow(<ReasonInput />);
|
render(<ReasonInput />);
|
||||||
});
|
});
|
||||||
describe('behavior', () => {
|
describe('behavior', () => {
|
||||||
it('initializes hook data', () => {
|
it('initializes hook data', () => {
|
||||||
expect(useReasonInputData).toHaveBeenCalled();
|
expect(useReasonInputData).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('render', () => {
|
describe('renders', () => {
|
||||||
test('snapshot', () => {
|
it('input correctly', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
||||||
const control = el.instance.findByType(Form.Control)[0];
|
expect(screen.getByRole('textbox')).toHaveValue(hookProps.value);
|
||||||
expect(control.props.value).toEqual(hookProps.value);
|
|
||||||
expect(control.props.onChange).toEqual(hookProps.onChange);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import { render } from '@testing-library/react';
|
|||||||
import useReasonInputData from './hooks';
|
import useReasonInputData from './hooks';
|
||||||
import ReasonInput, { controlTestId } from '.';
|
import ReasonInput, { controlTestId } from '.';
|
||||||
|
|
||||||
jest.unmock('react');
|
|
||||||
jest.unmock('@openedx/paragon');
|
|
||||||
jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
|
jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
|
||||||
|
|
||||||
const focus = jest.fn();
|
const focus = jest.fn();
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`OverrideTable component render snapshot 1`] = `
|
|
||||||
<DataTable
|
|
||||||
columns="test-columns"
|
|
||||||
data={
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"test": "data",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"andOther": "test-data",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"adjustedGrade": <AdjustedGradeInput />,
|
|
||||||
"date": {
|
|
||||||
"formatted": 2000-01-01T00:00:00.000Z,
|
|
||||||
},
|
|
||||||
"reason": <ReasonInput />,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
itemCount={3}
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { formatMessage } from 'testUtils';
|
|
||||||
|
|
||||||
import { gradeOverrideHistoryColumns as columns } from 'data/constants/app';
|
import { gradeOverrideHistoryColumns as columns } from 'data/constants/app';
|
||||||
import { selectors } from 'data/redux/hooks';
|
import { selectors } from 'data/redux/hooks';
|
||||||
@@ -16,6 +15,18 @@ jest.mock('data/redux/hooks', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('react', () => ({
|
||||||
|
...jest.requireActual('react'),
|
||||||
|
useContext: jest.fn(context => context),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform/i18n', () => ({
|
||||||
|
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||||
|
useIntl: jest.fn(() => ({
|
||||||
|
formatMessage: (message) => message.defaultMessage,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
selectors.grades.useHasOverrideErrors.mockReturnValue(false);
|
selectors.grades.useHasOverrideErrors.mockReturnValue(false);
|
||||||
const gradeOverrides = ['some', 'override', 'data'];
|
const gradeOverrides = ['some', 'override', 'data'];
|
||||||
const gradeData = { gradeOverrideHistoryResults: gradeOverrides };
|
const gradeData = { gradeOverrideHistoryResults: gradeOverrides };
|
||||||
@@ -44,22 +55,22 @@ describe('useOverrideTableData', () => {
|
|||||||
describe('columns', () => {
|
describe('columns', () => {
|
||||||
test('date column', () => {
|
test('date column', () => {
|
||||||
const { Header, accessor } = out.columns[0];
|
const { Header, accessor } = out.columns[0];
|
||||||
expect(Header).toEqual(formatMessage(messages.dateHeader));
|
expect(Header).toEqual(messages.dateHeader.defaultMessage);
|
||||||
expect(accessor).toEqual(columns.date);
|
expect(accessor).toEqual(columns.date);
|
||||||
});
|
});
|
||||||
test('grader column', () => {
|
test('grader column', () => {
|
||||||
const { Header, accessor } = out.columns[1];
|
const { Header, accessor } = out.columns[1];
|
||||||
expect(Header).toEqual(formatMessage(messages.graderHeader));
|
expect(Header).toEqual(messages.graderHeader.defaultMessage);
|
||||||
expect(accessor).toEqual(columns.grader);
|
expect(accessor).toEqual(columns.grader);
|
||||||
});
|
});
|
||||||
test('reason column', () => {
|
test('reason column', () => {
|
||||||
const { Header, accessor } = out.columns[2];
|
const { Header, accessor } = out.columns[2];
|
||||||
expect(Header).toEqual(formatMessage(messages.reasonHeader));
|
expect(Header).toEqual(messages.reasonHeader.defaultMessage);
|
||||||
expect(accessor).toEqual(columns.reason);
|
expect(accessor).toEqual(columns.reason);
|
||||||
});
|
});
|
||||||
test('adjustedGrade column', () => {
|
test('adjustedGrade column', () => {
|
||||||
const { Header, accessor } = out.columns[3];
|
const { Header, accessor } = out.columns[3];
|
||||||
expect(Header).toEqual(formatMessage(messages.adjustedGradeHeader));
|
expect(Header).toEqual(messages.adjustedGradeHeader.defaultMessage);
|
||||||
expect(accessor).toEqual(columns.adjustedGrade);
|
expect(accessor).toEqual(columns.adjustedGrade);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,65 +1,61 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { screen } from '@testing-library/react';
|
||||||
|
|
||||||
import { DataTable } from '@openedx/paragon';
|
|
||||||
|
|
||||||
import { formatDateForDisplay } from 'utils';
|
|
||||||
|
|
||||||
import AdjustedGradeInput from './AdjustedGradeInput';
|
|
||||||
import ReasonInput from './ReasonInput';
|
|
||||||
import useOverrideTableData from './hooks';
|
import useOverrideTableData from './hooks';
|
||||||
import OverrideTable from '.';
|
import OverrideTable from '.';
|
||||||
|
import { renderWithIntl } from '../../../../testUtilsExtra';
|
||||||
|
|
||||||
jest.mock('utils', () => ({
|
jest.mock('utils', () => ({
|
||||||
|
...jest.requireActual('utils'),
|
||||||
formatDateForDisplay: (date) => ({ formatted: date }),
|
formatDateForDisplay: (date) => ({ formatted: date }),
|
||||||
}));
|
}));
|
||||||
jest.mock('./hooks', () => jest.fn());
|
jest.mock('./hooks', () => jest.fn());
|
||||||
jest.mock('./AdjustedGradeInput', () => 'AdjustedGradeInput');
|
|
||||||
jest.mock('./ReasonInput', () => 'ReasonInput');
|
|
||||||
|
|
||||||
const hookProps = {
|
const hookProps = {
|
||||||
hide: false,
|
hide: false,
|
||||||
data: [
|
data: [
|
||||||
{ test: 'data' },
|
{ filename: 'data' },
|
||||||
{ andOther: 'test-data' },
|
{ resultsSummary: 'test-data' },
|
||||||
],
|
],
|
||||||
columns: 'test-columns',
|
columns: [{
|
||||||
|
Header: 'Gradebook',
|
||||||
|
accessor: 'filename',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Download Summary',
|
||||||
|
accessor: 'resultsSummary',
|
||||||
|
}],
|
||||||
};
|
};
|
||||||
useOverrideTableData.mockReturnValue(hookProps);
|
|
||||||
|
|
||||||
let el;
|
|
||||||
describe('OverrideTable component', () => {
|
describe('OverrideTable component', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest
|
jest
|
||||||
.clearAllMocks()
|
.clearAllMocks()
|
||||||
.useFakeTimers('modern')
|
.useFakeTimers('modern')
|
||||||
.setSystemTime(new Date('2000-01-01').getTime());
|
.setSystemTime(new Date('2000-01-01').getTime());
|
||||||
el = shallow(<OverrideTable />);
|
|
||||||
});
|
});
|
||||||
describe('behavior', () => {
|
describe('hooks', () => {
|
||||||
it('initializes hook data', () => {
|
it('initializes hook data', () => {
|
||||||
|
useOverrideTableData.mockReturnValue(hookProps);
|
||||||
|
renderWithIntl(<OverrideTable />);
|
||||||
expect(useOverrideTableData).toHaveBeenCalled();
|
expect(useOverrideTableData).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('render', () => {
|
describe('behavior', () => {
|
||||||
test('null render if hide', () => {
|
it('null render if hide', () => {
|
||||||
useOverrideTableData.mockReturnValueOnce({ ...hookProps, hide: true });
|
useOverrideTableData.mockReturnValue({ ...hookProps, hide: true });
|
||||||
el = shallow(<OverrideTable />);
|
renderWithIntl(<OverrideTable />);
|
||||||
expect(el.isEmptyRender()).toEqual(true);
|
expect(screen.queryByRole('table')).toBeNull();
|
||||||
});
|
});
|
||||||
test('snapshot', () => {
|
it('renders table with correct data', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
useOverrideTableData.mockReturnValue(hookProps);
|
||||||
const table = el.instance.findByType(DataTable)[0];
|
renderWithIntl(<OverrideTable />);
|
||||||
expect(table.props.columns).toEqual(hookProps.columns);
|
const table = screen.getByRole('table');
|
||||||
const data = [...table.props.data];
|
expect(table).toBeInTheDocument();
|
||||||
const inputRow = data.pop();
|
expect(screen.getByText(hookProps.columns[0].Header)).toBeInTheDocument();
|
||||||
const formattedDate = formatDateForDisplay(new Date());
|
expect(screen.getByText(hookProps.columns[1].Header)).toBeInTheDocument();
|
||||||
expect(data).toEqual(hookProps.data);
|
expect(screen.getByText(hookProps.data[0].filename)).toBeInTheDocument();
|
||||||
expect(inputRow).toMatchObject({
|
expect(screen.getByText(hookProps.data[1].resultsSummary)).toBeInTheDocument();
|
||||||
adjustedGrade: <AdjustedGradeInput />,
|
|
||||||
date: formattedDate,
|
|
||||||
reason: <ReasonInput />,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`HistoryHeader Component snapshot 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
className="grade-history-header grade-history-water"
|
|
||||||
>
|
|
||||||
Brita
|
|
||||||
:
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
hydration
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`ModalHeaders render snapshot 1`] = `
|
|
||||||
<div>
|
|
||||||
<HistoryHeader
|
|
||||||
id="assignment"
|
|
||||||
label="Assignment"
|
|
||||||
value="test-assignment-name"
|
|
||||||
/>
|
|
||||||
<HistoryHeader
|
|
||||||
id="student"
|
|
||||||
label="Student"
|
|
||||||
value="test-user-name"
|
|
||||||
/>
|
|
||||||
<HistoryHeader
|
|
||||||
id="original-grade"
|
|
||||||
label="Original Grade"
|
|
||||||
value="test-original-grade"
|
|
||||||
/>
|
|
||||||
<HistoryHeader
|
|
||||||
id="current-grade"
|
|
||||||
label="Current Grade"
|
|
||||||
value="test-current-grade"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`EditModal component render with error snapshot 1`] = `
|
|
||||||
<ModalDialog
|
|
||||||
hasCloseButton={true}
|
|
||||||
isFullscreenOnMobile={true}
|
|
||||||
isOpen="test-is-open"
|
|
||||||
onClose={[MockFunction hooks.onClose]}
|
|
||||||
size="xl"
|
|
||||||
title="Edit Grades"
|
|
||||||
>
|
|
||||||
<ModalDialog.Body>
|
|
||||||
<div>
|
|
||||||
<ModalHeaders />
|
|
||||||
<Alert
|
|
||||||
dismissible={false}
|
|
||||||
show={true}
|
|
||||||
variant="danger"
|
|
||||||
>
|
|
||||||
test-error
|
|
||||||
</Alert>
|
|
||||||
<OverrideTable />
|
|
||||||
<div>
|
|
||||||
Showing most recent actions (max 5). To see more, please contact support
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
Note: Once you save, your changes will be visible to students.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ModalDialog.Body>
|
|
||||||
<ModalDialog.Footer>
|
|
||||||
<ActionRow>
|
|
||||||
<ModalDialog.CloseButton
|
|
||||||
variant="tertiary"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</ModalDialog.CloseButton>
|
|
||||||
<Button
|
|
||||||
onClick={[MockFunction hooks.handleAdjustedGradeClick]}
|
|
||||||
variant="primary"
|
|
||||||
>
|
|
||||||
Save Grades
|
|
||||||
</Button>
|
|
||||||
</ActionRow>
|
|
||||||
</ModalDialog.Footer>
|
|
||||||
</ModalDialog>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`EditModal component render without error snapshot 1`] = `
|
|
||||||
<ModalDialog
|
|
||||||
hasCloseButton={true}
|
|
||||||
isFullscreenOnMobile={true}
|
|
||||||
isOpen="test-is-open"
|
|
||||||
onClose={[MockFunction hooks.onClose]}
|
|
||||||
size="xl"
|
|
||||||
title="Edit Grades"
|
|
||||||
>
|
|
||||||
<ModalDialog.Body>
|
|
||||||
<div>
|
|
||||||
<ModalHeaders />
|
|
||||||
<Alert
|
|
||||||
dismissible={false}
|
|
||||||
show={false}
|
|
||||||
variant="danger"
|
|
||||||
/>
|
|
||||||
<OverrideTable />
|
|
||||||
<div>
|
|
||||||
Showing most recent actions (max 5). To see more, please contact support
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
Note: Once you save, your changes will be visible to students.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ModalDialog.Body>
|
|
||||||
<ModalDialog.Footer>
|
|
||||||
<ActionRow>
|
|
||||||
<ModalDialog.CloseButton
|
|
||||||
variant="tertiary"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</ModalDialog.CloseButton>
|
|
||||||
<Button
|
|
||||||
onClick={[MockFunction hooks.handleAdjustedGradeClick]}
|
|
||||||
variant="primary"
|
|
||||||
>
|
|
||||||
Save Grades
|
|
||||||
</Button>
|
|
||||||
</ActionRow>
|
|
||||||
</ModalDialog.Footer>
|
|
||||||
</ModalDialog>
|
|
||||||
`;
|
|
||||||
@@ -1,126 +1,102 @@
|
|||||||
import React from 'react';
|
import { screen } from '@testing-library/react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
import {
|
|
||||||
ActionRow,
|
|
||||||
ModalDialog,
|
|
||||||
} from '@openedx/paragon';
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
|
||||||
|
|
||||||
import { formatMessage } from 'testUtils';
|
|
||||||
|
|
||||||
import ModalHeaders from './ModalHeaders';
|
|
||||||
import OverrideTable from './OverrideTable';
|
|
||||||
import useEditModalData from './hooks';
|
import useEditModalData from './hooks';
|
||||||
import EditModal from '.';
|
import EditModal from '.';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
import { renderWithIntl } from '../../../testUtilsExtra';
|
||||||
|
|
||||||
jest.mock('./hooks', () => jest.fn());
|
jest.mock('./hooks', () => jest.fn());
|
||||||
jest.mock('./ModalHeaders', () => 'ModalHeaders');
|
jest.mock('./ModalHeaders', () => jest.fn(() => <div>ModalHeaders</div>));
|
||||||
jest.mock('./OverrideTable', () => 'OverrideTable');
|
jest.mock('./OverrideTable', () => jest.fn(() => <div>OverrideTable</div>));
|
||||||
|
|
||||||
const hookProps = {
|
const hookProps = {
|
||||||
onClose: jest.fn().mockName('hooks.onClose'),
|
onClose: jest.fn().mockName('hooks.onClose'),
|
||||||
error: 'test-error',
|
error: 'test-error',
|
||||||
handleAdjustedGradeClick: jest.fn().mockName('hooks.handleAdjustedGradeClick'),
|
handleAdjustedGradeClick: jest.fn().mockName('hooks.handleAdjustedGradeClick'),
|
||||||
isOpen: 'test-is-open',
|
isOpen: true,
|
||||||
};
|
};
|
||||||
useEditModalData.mockReturnValue(hookProps);
|
|
||||||
|
|
||||||
let el;
|
|
||||||
describe('EditModal component', () => {
|
describe('EditModal component', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
el = shallow(<EditModal />);
|
|
||||||
});
|
});
|
||||||
describe('behavior', () => {
|
describe('behavior', () => {
|
||||||
it('initializes intl hook', () => {
|
|
||||||
expect(useIntl).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
it('initializes component hooks', () => {
|
it('initializes component hooks', () => {
|
||||||
|
useEditModalData.mockReturnValue(hookProps);
|
||||||
|
renderWithIntl(<EditModal />);
|
||||||
expect(useEditModalData).toHaveBeenCalled();
|
expect(useEditModalData).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('render', () => {
|
describe('renders', () => {
|
||||||
test('modal props', () => {
|
const testModal = () => {
|
||||||
const modalProps = el.instance.findByType(ModalDialog)[0].props;
|
it('modal properly', () => {
|
||||||
expect(modalProps.title).toEqual(formatMessage(messages.title));
|
const modal = screen.getByRole('dialog', { title: messages.title.defaultMessage });
|
||||||
expect(modalProps.isOpen).toEqual(hookProps.isOpen);
|
expect(modal).toBeInTheDocument();
|
||||||
expect(modalProps.onClose).toEqual(hookProps.onClose);
|
});
|
||||||
});
|
it('triggers onClose when closed', async () => {
|
||||||
const loadBody = () => {
|
const user = userEvent.setup();
|
||||||
const body = el.instance.findByType(ModalDialog)[0].children[0];
|
const closeButton = screen.getByRole('button', { name: messages.closeText.defaultMessage });
|
||||||
const { children } = body.children[0];
|
await user.click(closeButton);
|
||||||
return { body, children };
|
expect(hookProps.onClose).toHaveBeenCalled();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
const testBody = () => {
|
const testBody = () => {
|
||||||
test('type', () => {
|
it('headers row', () => {
|
||||||
const { body } = loadBody();
|
const headers = screen.getByText('ModalHeaders');
|
||||||
expect(body.type).toEqual('ModalDialog.Body');
|
expect(headers).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
test('headers row', () => {
|
it('table row', () => {
|
||||||
const { children } = loadBody();
|
const table = screen.getByText('OverrideTable');
|
||||||
expect(children[0]).toMatchObject(shallow(<ModalHeaders />));
|
expect(table).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
test('table row', () => {
|
it('messages', () => {
|
||||||
const { children } = loadBody();
|
const visibilityMessage = screen.getByText(messages.visibility.defaultMessage);
|
||||||
expect(children[2]).toMatchObject(shallow(<OverrideTable />));
|
const saveVisibilityMessage = screen.getByText(messages.saveVisibility.defaultMessage);
|
||||||
});
|
expect(visibilityMessage).toBeInTheDocument();
|
||||||
test('messages', () => {
|
expect(saveVisibilityMessage).toBeInTheDocument();
|
||||||
const { children } = loadBody();
|
|
||||||
expect(children[3].children[0].el).toEqual(formatMessage(messages.visibility));
|
|
||||||
expect(children[4].children[0].el).toEqual(formatMessage(messages.saveVisibility));
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const testFooter = () => {
|
const testFooter = () => {
|
||||||
let footer;
|
it('adjusted grade button', async () => {
|
||||||
beforeEach(() => {
|
const user = userEvent.setup();
|
||||||
footer = el.instance.findByType(ModalDialog)[0].children;
|
const saveGradeButton = screen.getByRole('button', { name: messages.saveGrade.defaultMessage });
|
||||||
|
expect(saveGradeButton).toBeInTheDocument();
|
||||||
|
await user.click(saveGradeButton);
|
||||||
|
expect(hookProps.handleAdjustedGradeClick).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
test('type', () => {
|
it('close button', async () => {
|
||||||
expect(footer[1].type).toEqual('ModalDialog.Footer');
|
const user = userEvent.setup();
|
||||||
});
|
const cancelButton = screen.getByRole('button', { name: messages.closeText.defaultMessage });
|
||||||
test('contains action row', () => {
|
expect(cancelButton).toBeInTheDocument();
|
||||||
expect(footer[1].children[0].type).toEqual('ActionRow');
|
await user.click(cancelButton);
|
||||||
});
|
expect(hookProps.onClose).toHaveBeenCalled();
|
||||||
test('close button', () => {
|
|
||||||
const button = footer[1].findByType(ActionRow)[0].children[0];
|
|
||||||
expect(button.children[0].el).toEqual(formatMessage(messages.closeText));
|
|
||||||
expect(button.type).toEqual('ModalDialog.CloseButton');
|
|
||||||
});
|
|
||||||
test('adjusted grade button', () => {
|
|
||||||
const button = footer[1].findByType(ActionRow)[0].children[1];
|
|
||||||
expect(button.children[0].el).toEqual(formatMessage(messages.saveGrade));
|
|
||||||
expect(button.type).toEqual('Button');
|
|
||||||
expect(button.props.onClick).toEqual(hookProps.handleAdjustedGradeClick);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
describe('without error', () => {
|
describe('without error', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
useEditModalData.mockReturnValueOnce({ ...hookProps, error: undefined });
|
useEditModalData.mockReturnValueOnce({ ...hookProps, error: undefined });
|
||||||
el = shallow(<EditModal />);
|
renderWithIntl(<EditModal />);
|
||||||
});
|
|
||||||
test('snapshot', () => {
|
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
|
||||||
});
|
});
|
||||||
|
testModal();
|
||||||
testBody();
|
testBody();
|
||||||
testFooter();
|
testFooter();
|
||||||
test('alert row', () => {
|
test('alert row', () => {
|
||||||
const alert = loadBody().children[1];
|
const alert = screen.queryByRole('alert');
|
||||||
expect(alert.type).toEqual('Alert');
|
expect(alert).toBeNull();
|
||||||
expect(alert.props.show).toEqual(false);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('with error', () => {
|
describe('with error', () => {
|
||||||
test('snapshot', () => {
|
beforeEach(() => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
useEditModalData.mockReturnValue(hookProps);
|
||||||
|
renderWithIntl(<EditModal />);
|
||||||
});
|
});
|
||||||
|
testModal();
|
||||||
testBody();
|
testBody();
|
||||||
test('alert row', () => {
|
test('alert row', () => {
|
||||||
const alert = loadBody().children[1];
|
const alert = screen.getByRole('alert');
|
||||||
expect(alert.type).toEqual('Alert');
|
expect(alert).toBeInTheDocument();
|
||||||
expect(alert.props.show).toEqual(true);
|
expect(alert).toHaveTextContent(hookProps.error);
|
||||||
expect(alert.children[0].el).toEqual(hookProps.error);
|
|
||||||
});
|
});
|
||||||
testFooter();
|
testFooter();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { screen } from '@testing-library/react';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
|
||||||
|
|
||||||
import { formatMessage } from 'testUtils';
|
import { formatMessage } from 'testUtils';
|
||||||
import { Button } from '@openedx/paragon';
|
|
||||||
import { selectors } from 'data/redux/hooks';
|
import { selectors } from 'data/redux/hooks';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
import FilterBadge from './FilterBadge';
|
import FilterBadge from './FilterBadge';
|
||||||
|
import { renderWithIntl } from '../../../testUtilsExtra';
|
||||||
|
|
||||||
jest.mock('@openedx/paragon', () => ({
|
|
||||||
Button: () => 'Button',
|
|
||||||
}));
|
|
||||||
jest.mock('data/redux/hooks', () => ({
|
jest.mock('data/redux/hooks', () => ({
|
||||||
selectors: {
|
selectors: {
|
||||||
root: {
|
root: {
|
||||||
@@ -18,11 +15,12 @@ jest.mock('data/redux/hooks', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const handleClose = jest.fn(filters => ({ handleClose: filters }));
|
const handleClose = jest.fn();
|
||||||
const filterName = 'test-filter-name';
|
const filterName = 'test-filter-name';
|
||||||
|
|
||||||
const hookProps = {
|
const hookProps = {
|
||||||
displayName: {
|
displayName: {
|
||||||
|
id: 'test.id',
|
||||||
defaultMessage: 'a common name',
|
defaultMessage: 'a common name',
|
||||||
},
|
},
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
@@ -32,63 +30,52 @@ const hookProps = {
|
|||||||
};
|
};
|
||||||
selectors.root.useFilterBadgeConfig.mockReturnValue(hookProps);
|
selectors.root.useFilterBadgeConfig.mockReturnValue(hookProps);
|
||||||
|
|
||||||
let el;
|
|
||||||
describe('FilterBadge', () => {
|
describe('FilterBadge', () => {
|
||||||
beforeEach(() => {
|
describe('hooks', () => {
|
||||||
el = shallow(<FilterBadge {...{ handleClose, filterName }} />);
|
beforeEach(() => {
|
||||||
});
|
renderWithIntl(<FilterBadge {...{ handleClose, filterName }} />);
|
||||||
describe('behavior', () => {
|
|
||||||
it('initializes intl hook', () => {
|
|
||||||
expect(useIntl).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
it('initializes redux hooks', () => {
|
it('initializes redux hooks', () => {
|
||||||
expect(selectors.root.useFilterBadgeConfig).toHaveBeenCalledWith(filterName);
|
expect(selectors.root.useFilterBadgeConfig).toHaveBeenCalledWith(filterName);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
const testDisplayName = () => {
|
it('empty render if isDefault', () => {
|
||||||
test('formatted display name appears on badge', () => {
|
|
||||||
expect(el.instance.findByTestId('display-name')[0].children[0].el).toEqual(formatMessage(hookProps.displayName));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const testCloseButton = () => {
|
|
||||||
test('close button forwards close method', () => {
|
|
||||||
expect(el.instance.findByType(Button)[0].props.onClick).toEqual(handleClose(hookProps.connectedFilters));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
test('empty render if isDefault', () => {
|
|
||||||
selectors.root.useFilterBadgeConfig.mockReturnValueOnce({
|
selectors.root.useFilterBadgeConfig.mockReturnValueOnce({
|
||||||
...hookProps,
|
...hookProps,
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
});
|
});
|
||||||
el = shallow(<FilterBadge {...{ handleClose, filterName }} />);
|
renderWithIntl(<FilterBadge {...{ handleClose, filterName }} />);
|
||||||
expect(el.isEmptyRender()).toEqual(true);
|
expect(screen.queryByText(hookProps.displayName)).toBeNull();
|
||||||
});
|
});
|
||||||
describe('hide Value', () => {
|
describe('hide Value', () => {
|
||||||
beforeEach(() => {
|
it('renders display name, value is not shown and close button has correct behavior', async () => {
|
||||||
selectors.root.useFilterBadgeConfig.mockReturnValueOnce({
|
selectors.root.useFilterBadgeConfig.mockReturnValueOnce({
|
||||||
...hookProps,
|
...hookProps,
|
||||||
hideValue: true,
|
hideValue: true,
|
||||||
});
|
});
|
||||||
el = shallow(<FilterBadge {...{ handleClose, filterName }} />);
|
renderWithIntl(<FilterBadge {...{ handleClose, filterName }} />);
|
||||||
});
|
const user = userEvent.setup();
|
||||||
testDisplayName();
|
expect(screen.getByTestId('display-name')).toHaveTextContent(formatMessage(hookProps.displayName));
|
||||||
testCloseButton();
|
expect(screen.queryByTestId('filter-value')).toHaveTextContent('');
|
||||||
test('snapshot', () => {
|
const button = screen.getByRole('button', { name: /close/i });
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
await user.click(button);
|
||||||
});
|
expect(handleClose).toHaveBeenCalledWith(hookProps.connectedFilters);
|
||||||
test('value is note present in the badge', () => {
|
|
||||||
expect(el.instance.findByTestId('filter-value')[0].children).toHaveLength(0);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('do not hide value', () => {
|
describe('do not hide value', () => {
|
||||||
testDisplayName();
|
it('renders display name and value, and close button has correct behavior', async () => {
|
||||||
testCloseButton();
|
selectors.root.useFilterBadgeConfig.mockReturnValueOnce({
|
||||||
test('snapshot', () => {
|
...hookProps,
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
hideValue: false,
|
||||||
});
|
});
|
||||||
test('value is present in the badge', () => {
|
renderWithIntl(<FilterBadge {...{ handleClose, filterName }} />);
|
||||||
expect(el.instance.findByTestId('filter-value')[0].children[0].el).toBe(`: ${hookProps.value}`);
|
const user = userEvent.setup();
|
||||||
|
expect(screen.getByTestId('display-name')).toHaveTextContent(formatMessage(hookProps.displayName));
|
||||||
|
expect(screen.getByTestId('filter-value')).toHaveTextContent(`: ${hookProps.value}`);
|
||||||
|
const button = screen.getByRole('button', { name: /close/i });
|
||||||
|
await user.click(button);
|
||||||
|
expect(handleClose).toHaveBeenCalledWith(hookProps.connectedFilters);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`FilterBadge render do not hide value snapshot 1`] = `
|
|
||||||
<div>
|
|
||||||
<span
|
|
||||||
className="badge badge-info"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
data-testid="display-name"
|
|
||||||
>
|
|
||||||
a common name
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
data-testid="filter-value"
|
|
||||||
>
|
|
||||||
: a common value
|
|
||||||
</span>
|
|
||||||
<Button
|
|
||||||
aria-label="close"
|
|
||||||
className="btn-info"
|
|
||||||
onClick={
|
|
||||||
{
|
|
||||||
"handleClose": [
|
|
||||||
"some",
|
|
||||||
"filters",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</span>
|
|
||||||
</Button>
|
|
||||||
</span>
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`FilterBadge render hide Value snapshot 1`] = `
|
|
||||||
<div>
|
|
||||||
<span
|
|
||||||
className="badge badge-info"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
data-testid="display-name"
|
|
||||||
>
|
|
||||||
a common name
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
data-testid="filter-value"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
aria-label="close"
|
|
||||||
className="btn-info"
|
|
||||||
onClick={
|
|
||||||
{
|
|
||||||
"handleClose": [
|
|
||||||
"some",
|
|
||||||
"filters",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</span>
|
|
||||||
</Button>
|
|
||||||
</span>
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`FilterBadges component snapshot - has a filterbadge with handleClose for each filter in badgeOrder 1`] = `
|
|
||||||
<div>
|
|
||||||
<FilterBadge
|
|
||||||
filterName="filter1"
|
|
||||||
handleClose={[MockFunction this.props.handleClose]}
|
|
||||||
key="filter1"
|
|
||||||
/>
|
|
||||||
<FilterBadge
|
|
||||||
filterName="filter2"
|
|
||||||
handleClose={[MockFunction this.props.handleClose]}
|
|
||||||
key="filter2"
|
|
||||||
/>
|
|
||||||
<FilterBadge
|
|
||||||
filterName="filter3"
|
|
||||||
handleClose={[MockFunction this.props.handleClose]}
|
|
||||||
key="filter3"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
/* eslint-disable import/no-named-as-default */
|
/* eslint-disable import/no-named-as-default */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
import FilterBadges from '.';
|
import FilterBadges from '.';
|
||||||
import FilterBadge from './FilterBadge';
|
|
||||||
|
|
||||||
jest.mock('./FilterBadge', () => 'FilterBadge');
|
|
||||||
|
|
||||||
const order = ['filter1', 'filter2', 'filter3'];
|
const order = ['filter1', 'filter2', 'filter3'];
|
||||||
jest.mock('data/constants/filters', () => ({
|
jest.mock('data/constants/filters', () => ({
|
||||||
@@ -13,24 +12,24 @@ jest.mock('data/constants/filters', () => ({
|
|||||||
badgeOrder: order,
|
badgeOrder: order,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/button-has-type
|
||||||
|
jest.mock('./FilterBadge', () => jest.fn(({ filterName, handleClose }) => <button onClick={handleClose}>{filterName}</button>));
|
||||||
|
|
||||||
|
const handleClose = jest.fn();
|
||||||
|
|
||||||
describe('FilterBadges', () => {
|
describe('FilterBadges', () => {
|
||||||
describe('component', () => {
|
describe('component', () => {
|
||||||
let el;
|
it('has a filterbadge with handleClose for each filter in badgeOrder', async () => {
|
||||||
let handleClose;
|
render(<FilterBadges handleClose={handleClose} />);
|
||||||
beforeEach(() => {
|
const user = userEvent.setup();
|
||||||
handleClose = jest.fn().mockName('this.props.handleClose');
|
const badge1 = screen.getByText(order[0]);
|
||||||
el = shallow(<FilterBadges handleClose={handleClose} />);
|
const badge2 = screen.getByText(order[1]);
|
||||||
});
|
const badge3 = screen.getByText(order[2]);
|
||||||
test('snapshot - has a filterbadge with handleClose for each filter in badgeOrder', () => {
|
expect(badge1).toBeInTheDocument();
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
expect(badge2).toBeInTheDocument();
|
||||||
});
|
expect(badge3).toBeInTheDocument();
|
||||||
test('has a filterbadge with handleClose for each filter in badgeOrder', () => {
|
await user.click(badge1);
|
||||||
const badgeProps = el.instance.findByType(FilterBadge).map(badgeEl => badgeEl.props);
|
expect(handleClose).toHaveBeenCalled();
|
||||||
// key prop is not rendered by react
|
|
||||||
expect(badgeProps[0]).toMatchObject({ filterName: order[0], handleClose });
|
|
||||||
expect(badgeProps[1]).toMatchObject({ filterName: order[1], handleClose });
|
|
||||||
expect(badgeProps[2]).toMatchObject({ filterName: order[2], handleClose });
|
|
||||||
expect(badgeProps.length).toEqual(3);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`FilterMenuToggle component render snapshot 1`] = `
|
|
||||||
<Button
|
|
||||||
className="btn-primary align-self-start"
|
|
||||||
id="edit-filters-btn"
|
|
||||||
onClick={[MockFunction hooks.toggleFilterMenu]}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
className="mr-1"
|
|
||||||
src="FilterAlt"
|
|
||||||
/>
|
|
||||||
|
|
||||||
Edit Filters
|
|
||||||
</Button>
|
|
||||||
`;
|
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
import React from 'react';
|
import { screen } from '@testing-library/react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
|
||||||
|
|
||||||
import { formatMessage } from 'testUtils';
|
import { formatMessage } from 'testUtils';
|
||||||
import { thunkActions } from 'data/redux/hooks';
|
import { thunkActions } from 'data/redux/hooks';
|
||||||
|
|
||||||
import FilterMenuToggle from '.';
|
import FilterMenuToggle from '.';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
import { renderWithIntl } from '../../../testUtilsExtra';
|
||||||
|
|
||||||
jest.mock('data/redux/hooks', () => ({
|
jest.mock('data/redux/hooks', () => ({
|
||||||
thunkActions: {
|
thunkActions: {
|
||||||
@@ -22,26 +20,23 @@ jest.mock('data/redux/hooks', () => ({
|
|||||||
const toggleFilterMenu = jest.fn().mockName('hooks.toggleFilterMenu');
|
const toggleFilterMenu = jest.fn().mockName('hooks.toggleFilterMenu');
|
||||||
thunkActions.app.filterMenu.useToggleMenu.mockReturnValue(toggleFilterMenu);
|
thunkActions.app.filterMenu.useToggleMenu.mockReturnValue(toggleFilterMenu);
|
||||||
|
|
||||||
let el;
|
|
||||||
describe('FilterMenuToggle component', () => {
|
describe('FilterMenuToggle component', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
el = shallow(<FilterMenuToggle />);
|
renderWithIntl(<FilterMenuToggle />);
|
||||||
});
|
});
|
||||||
describe('behavior', () => {
|
describe('behavior', () => {
|
||||||
it('initializes intl hook', () => {
|
|
||||||
expect(useIntl).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
it('initializes redux hooks', () => {
|
it('initializes redux hooks', () => {
|
||||||
expect(thunkActions.app.filterMenu.useToggleMenu).toHaveBeenCalled();
|
expect(thunkActions.app.filterMenu.useToggleMenu).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('render', () => {
|
describe('renders', () => {
|
||||||
test('snapshot', () => {
|
it('button and triggers click', async () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
const user = userEvent.setup();
|
||||||
expect(el.instance.type).toEqual('Button');
|
const button = screen.getByRole('button', { name: formatMessage(messages.editFilters) });
|
||||||
expect(el.instance.props.onClick).toEqual(toggleFilterMenu);
|
expect(button).toBeInTheDocument();
|
||||||
expect(el.instance.children[2].el).toContain(formatMessage(messages.editFilters));
|
await user.click(button);
|
||||||
|
expect(toggleFilterMenu).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`FilteredUsersLabel component render snapshot 1`] = `
|
|
||||||
<format-message-function
|
|
||||||
message={
|
|
||||||
{
|
|
||||||
"defaultMessage": "Showing {filteredUsers} of {totalUsers} total learners",
|
|
||||||
"description": "Users visibility label",
|
|
||||||
"id": "gradebook.GradesTab.usersVisibilityLabel",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
values={
|
|
||||||
{
|
|
||||||
"filteredUsers": <BoldText
|
|
||||||
text={100}
|
|
||||||
/>,
|
|
||||||
"totalUsers": <BoldText
|
|
||||||
text={123}
|
|
||||||
/>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
@@ -1,13 +1,9 @@
|
|||||||
import React from 'react';
|
import { screen } from '@testing-library/react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
|
||||||
|
|
||||||
import { formatMessage } from 'testUtils';
|
|
||||||
import { selectors } from 'data/redux/hooks';
|
import { selectors } from 'data/redux/hooks';
|
||||||
|
|
||||||
import FilteredUsersLabel, { BoldText } from '.';
|
import FilteredUsersLabel from '.';
|
||||||
import messages from './messages';
|
import { renderWithIntl } from '../../../testUtilsExtra';
|
||||||
|
|
||||||
jest.mock('data/redux/hooks', () => ({
|
jest.mock('data/redux/hooks', () => ({
|
||||||
selectors: {
|
selectors: {
|
||||||
@@ -23,34 +19,29 @@ const userCounts = {
|
|||||||
};
|
};
|
||||||
selectors.grades.useUserCounts.mockReturnValue(userCounts);
|
selectors.grades.useUserCounts.mockReturnValue(userCounts);
|
||||||
|
|
||||||
let el;
|
|
||||||
describe('FilteredUsersLabel component', () => {
|
describe('FilteredUsersLabel component', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
el = shallow(<FilteredUsersLabel />);
|
|
||||||
});
|
});
|
||||||
describe('behavior', () => {
|
describe('behavior', () => {
|
||||||
it('initializes intl hook', () => {
|
|
||||||
expect(useIntl).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
it('initializes redux hooks', () => {
|
it('initializes redux hooks', () => {
|
||||||
|
renderWithIntl(<FilteredUsersLabel />);
|
||||||
expect(selectors.grades.useUserCounts).toHaveBeenCalled();
|
expect(selectors.grades.useUserCounts).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
test('null render if totalUsersCount is 0', () => {
|
it('null render if totalUsersCount is 0', () => {
|
||||||
selectors.grades.useUserCounts.mockReturnValueOnce({
|
selectors.grades.useUserCounts.mockReturnValueOnce({
|
||||||
...userCounts,
|
...userCounts,
|
||||||
totalUsersCount: 0,
|
totalUsersCount: 0,
|
||||||
});
|
});
|
||||||
expect(shallow(<FilteredUsersLabel />).isEmptyRender()).toEqual(true);
|
const { container } = renderWithIntl(<FilteredUsersLabel />);
|
||||||
|
expect(container.firstChild).toBeNull();
|
||||||
});
|
});
|
||||||
test('snapshot', () => {
|
it('renders users count correctly', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
renderWithIntl(<FilteredUsersLabel />);
|
||||||
expect(el.instance).toMatchObject(shallow(formatMessage(messages.visibilityLabel, {
|
expect(screen.getByText((text) => text.includes(userCounts.filteredUsersCount))).toBeInTheDocument();
|
||||||
filteredUsers: <BoldText text={userCounts.filteredUsersCount} />,
|
expect(screen.getByText((text) => text.includes(userCounts.totalUsersCount))).toBeInTheDocument();
|
||||||
totalUsers: <BoldText text={userCounts.totalUsersCount} />,
|
|
||||||
})));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import React from 'react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
|
||||||
|
|
||||||
import Fields from './Fields';
|
import Fields from './Fields';
|
||||||
|
|
||||||
describe('Gradebook Table Fields', () => {
|
describe('Gradebook Table Fields', () => {
|
||||||
describe('Username', () => {
|
describe('Username', () => {
|
||||||
let el;
|
|
||||||
const username = 'MyNameFromHere';
|
const username = 'MyNameFromHere';
|
||||||
describe('with external_user_key', () => {
|
describe('with external_user_key', () => {
|
||||||
const props = {
|
const props = {
|
||||||
@@ -13,40 +11,32 @@ describe('Gradebook Table Fields', () => {
|
|||||||
userKey: 'My name from another land',
|
userKey: 'My name from another land',
|
||||||
};
|
};
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
el = shallow(<Fields.Username {...props} />);
|
render(<Fields.Username {...props} />);
|
||||||
});
|
});
|
||||||
test('snapshot', () => {
|
it('wraps external user key and username', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
const usernameField = screen.getByText(username);
|
||||||
});
|
expect(usernameField).toBeInTheDocument();
|
||||||
test('wraps external user key and username', () => {
|
const userKeyField = screen.getByText(props.userKey);
|
||||||
expect(el.instance.findByType('span')[0].el).toMatchSnapshot();
|
expect(userKeyField).toBeInTheDocument();
|
||||||
const content = el.instance.findByType('span')[0].children[0];
|
|
||||||
expect(content.children[0].children[0].el).toEqual(username);
|
|
||||||
expect(content.children[1].children[0].el).toEqual(props.userKey);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('without external_user_key', () => {
|
describe('without external_user_key', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
el = shallow(<Fields.Username username={username} />);
|
render(<Fields.Username username={username} />);
|
||||||
});
|
});
|
||||||
test('snapshot', () => {
|
it('wraps username only', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
const usernameField = screen.getByText(username);
|
||||||
});
|
expect(usernameField).toBeInTheDocument();
|
||||||
test('wraps username only', () => {
|
|
||||||
const content = el.instance.findByType('span')[0].children[0];
|
|
||||||
expect(content.children[0].children[0].el).toEqual(username);
|
|
||||||
expect(content.children).toHaveLength(1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Text', () => {
|
describe('Text', () => {
|
||||||
const value = 'myTag@place.com';
|
const value = 'myTag@place.com';
|
||||||
test('snapshot', () => {
|
it('wraps entry value', () => {
|
||||||
expect(shallow(<Fields.Text value={value} />).snapshot).toMatchSnapshot();
|
render(<Fields.Text value={value} />);
|
||||||
});
|
const textElement = screen.getByText(value);
|
||||||
test('wraps entry value', () => {
|
expect(textElement).toBeInTheDocument();
|
||||||
expect(shallow(<Fields.Text value={value} />).instance.children[0].el).toEqual(value);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
import { selectors, thunkActions } from 'data/redux/hooks';
|
import { selectors, thunkActions } from 'data/redux/hooks';
|
||||||
import transforms from 'data/redux/transforms';
|
import transforms from 'data/redux/transforms';
|
||||||
@@ -47,7 +47,6 @@ selectors.grades.useGradeData.mockReturnValue({ gradeFormat });
|
|||||||
thunkActions.app.useSetModalStateFromTable.mockReturnValue(setModalState);
|
thunkActions.app.useSetModalStateFromTable.mockReturnValue(setModalState);
|
||||||
transforms.grades.subsectionGrade.mockReturnValue(subsectionGrade);
|
transforms.grades.subsectionGrade.mockReturnValue(subsectionGrade);
|
||||||
|
|
||||||
let el;
|
|
||||||
let out;
|
let out;
|
||||||
describe('GradeButton', () => {
|
describe('GradeButton', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -98,23 +97,24 @@ describe('GradeButton', () => {
|
|||||||
describe('frozen grades', () => {
|
describe('frozen grades', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
hookSpy.mockReturnValue({ ...hookProps, areGradesFrozen: true });
|
hookSpy.mockReturnValue({ ...hookProps, areGradesFrozen: true });
|
||||||
el = shallow(<GradeButton {...props} />);
|
render(<GradeButton {...props} />);
|
||||||
});
|
});
|
||||||
test('snapshot', () => {
|
it('renders only labels', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
const label = screen.getByText(hookProps.label);
|
||||||
expect(el.instance.el).toEqual(hookProps.label);
|
expect(label).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('not frozen grades', () => {
|
describe('not frozen grades', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
hookSpy.mockReturnValue(hookProps);
|
hookSpy.mockReturnValue(hookProps);
|
||||||
el = shallow(<GradeButton {...props} />);
|
render(<GradeButton {...props} />);
|
||||||
});
|
});
|
||||||
test('snapshot', () => {
|
it('renders button', async () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
const user = userEvent.setup();
|
||||||
expect(el.instance.type).toEqual('Button');
|
const button = screen.getByRole('button', { name: hookProps.label });
|
||||||
expect(el.instance.props.onClick).toEqual(hookProps.onClick);
|
expect(button).toBeInTheDocument();
|
||||||
expect(el.instance.children[0].el).toEqual(hookProps.label);
|
await user.click(button);
|
||||||
|
expect(hookProps.onClick).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import React from 'react';
|
import { screen } from '@testing-library/react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
|
||||||
import { getLocale } from '@edx/frontend-platform/i18n';
|
import { getLocale } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { OverlayTrigger } from '@openedx/paragon';
|
|
||||||
|
|
||||||
import LabelReplacements from './LabelReplacements';
|
import LabelReplacements from './LabelReplacements';
|
||||||
|
import messages from './messages';
|
||||||
|
import { renderWithIntl } from '../../../testUtilsExtra';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
TotalGradeLabelReplacement,
|
TotalGradeLabelReplacement,
|
||||||
@@ -12,52 +10,36 @@ const {
|
|||||||
MastersOnlyLabelReplacement,
|
MastersOnlyLabelReplacement,
|
||||||
} = LabelReplacements;
|
} = LabelReplacements;
|
||||||
|
|
||||||
jest.mock('@openedx/paragon', () => ({
|
jest.mock('@edx/frontend-platform/i18n', () => ({
|
||||||
Icon: () => 'Icon',
|
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||||
OverlayTrigger: () => 'OverlayTrigger',
|
getLocale: jest.fn(),
|
||||||
Tooltip: () => 'Tooltip',
|
isRtl: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('LabelReplacements', () => {
|
describe('LabelReplacements', () => {
|
||||||
describe('TotalGradeLabelReplacement', () => {
|
describe('TotalGradeLabelReplacement', () => {
|
||||||
let el;
|
getLocale.mockImplementation(() => 'en');
|
||||||
beforeEach(() => {
|
renderWithIntl(<TotalGradeLabelReplacement />);
|
||||||
el = shallow(<TotalGradeLabelReplacement />);
|
it('displays overlay tooltip', () => {
|
||||||
});
|
const tooltip = screen.getByText(messages.totalGradePercentage.defaultMessage);
|
||||||
test('snapshot', () => {
|
expect(tooltip).toBeInTheDocument();
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
test('displays overlay tooltip', () => {
|
|
||||||
expect(el.instance.findByType(OverlayTrigger)[0].props.overlay).toMatchSnapshot();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('UsernameLabelReplacement', () => {
|
describe('UsernameLabelReplacement', () => {
|
||||||
test('snapshot', () => {
|
it('renders correctly', () => {
|
||||||
expect(shallow(<UsernameLabelReplacement />).snapshot).toMatchSnapshot();
|
renderWithIntl(<UsernameLabelReplacement />);
|
||||||
|
expect(screen.getByText(messages.usernameHeading.defaultMessage)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('MastersOnlyLabelReplacement', () => {
|
describe('MastersOnlyLabelReplacement', () => {
|
||||||
test('snapshot', () => {
|
it('renders correctly', () => {
|
||||||
const message = {
|
const message = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
defaultMessage: 'defaultMessAge',
|
defaultMessage: 'defaultMessAge',
|
||||||
description: 'desCripTion',
|
description: 'desCripTion',
|
||||||
};
|
};
|
||||||
expect(shallow(<MastersOnlyLabelReplacement {...message} />).snapshot).toMatchSnapshot();
|
renderWithIntl(<MastersOnlyLabelReplacement {...message} />);
|
||||||
|
expect(screen.getByText(message.defaultMessage)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('snapshot', () => {
|
|
||||||
let el;
|
|
||||||
test('right to left overlay placement', () => {
|
|
||||||
getLocale.mockImplementation(() => 'en');
|
|
||||||
el = shallow(<TotalGradeLabelReplacement />);
|
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
test('left to right overlay placement', () => {
|
|
||||||
getLocale.mockImplementation(() => 'ar');
|
|
||||||
el = shallow(<TotalGradeLabelReplacement />);
|
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`Gradebook Table Fields Text snapshot 1`] = `
|
|
||||||
<span
|
|
||||||
className="wrap-text-in-cell"
|
|
||||||
>
|
|
||||||
myTag@place.com
|
|
||||||
</span>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`Gradebook Table Fields Username with external_user_key snapshot 1`] = `
|
|
||||||
<div>
|
|
||||||
<span
|
|
||||||
className="wrap-text-in-cell"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
MyNameFromHere
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="student-key"
|
|
||||||
>
|
|
||||||
My name from another land
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`Gradebook Table Fields Username with external_user_key wraps external user key and username 1`] = `
|
|
||||||
{
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"children": [
|
|
||||||
"MyNameFromHere",
|
|
||||||
],
|
|
||||||
"props": {},
|
|
||||||
"type": "div",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"children": [
|
|
||||||
"My name from another land",
|
|
||||||
],
|
|
||||||
"props": {
|
|
||||||
"className": "student-key",
|
|
||||||
},
|
|
||||||
"type": "div",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"props": {},
|
|
||||||
"type": "div",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"props": {
|
|
||||||
"className": "wrap-text-in-cell",
|
|
||||||
},
|
|
||||||
"type": "span",
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`Gradebook Table Fields Username without external_user_key snapshot 1`] = `
|
|
||||||
<div>
|
|
||||||
<span
|
|
||||||
className="wrap-text-in-cell"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
MyNameFromHere
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`GradeButton component frozen grades snapshot 1`] = `"test-label"`;
|
|
||||||
|
|
||||||
exports[`GradeButton component not frozen grades snapshot 1`] = `
|
|
||||||
<Button
|
|
||||||
className="btn-header grade-button"
|
|
||||||
onClick={[MockFunction hooks.onClick]}
|
|
||||||
variant="link"
|
|
||||||
>
|
|
||||||
test-label
|
|
||||||
</Button>
|
|
||||||
`;
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`LabelReplacements MastersOnlyLabelReplacement snapshot 1`] = `
|
|
||||||
<div>
|
|
||||||
defaultMessAge
|
|
||||||
<span
|
|
||||||
className="font-weight-normal"
|
|
||||||
>
|
|
||||||
*
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`LabelReplacements TotalGradeLabelReplacement displays overlay tooltip 1`] = `
|
|
||||||
<Tooltip
|
|
||||||
id="course-grade-tooltip"
|
|
||||||
>
|
|
||||||
Total Grade values are always displayed as a percentage
|
|
||||||
</Tooltip>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`LabelReplacements TotalGradeLabelReplacement snapshot 1`] = `
|
|
||||||
<div>
|
|
||||||
<OverlayTrigger
|
|
||||||
key="left-basic"
|
|
||||||
overlay={
|
|
||||||
<Tooltip
|
|
||||||
id="course-grade-tooltip"
|
|
||||||
>
|
|
||||||
Total Grade values are always displayed as a percentage
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
placement="left"
|
|
||||||
trigger={
|
|
||||||
[
|
|
||||||
"hover",
|
|
||||||
"focus",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
Total Grade (%)
|
|
||||||
<div
|
|
||||||
id="courseGradeTooltipIcon"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
className="fa fa-info-circle"
|
|
||||||
screenReaderText="Total Grade values are always displayed as a percentage"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</OverlayTrigger>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`LabelReplacements UsernameLabelReplacement snapshot 1`] = `
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
Username
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="font-weight-normal student-key"
|
|
||||||
>
|
|
||||||
Student Key
|
|
||||||
<span
|
|
||||||
className="font-weight-normal"
|
|
||||||
>
|
|
||||||
*
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`snapshot left to right overlay placement 1`] = `
|
|
||||||
<div>
|
|
||||||
<OverlayTrigger
|
|
||||||
key="left-basic"
|
|
||||||
overlay={
|
|
||||||
<Tooltip
|
|
||||||
id="course-grade-tooltip"
|
|
||||||
>
|
|
||||||
Total Grade values are always displayed as a percentage
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
placement="right"
|
|
||||||
trigger={
|
|
||||||
[
|
|
||||||
"hover",
|
|
||||||
"focus",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
Total Grade (%)
|
|
||||||
<div
|
|
||||||
id="courseGradeTooltipIcon"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
className="fa fa-info-circle"
|
|
||||||
screenReaderText="Total Grade values are always displayed as a percentage"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</OverlayTrigger>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`snapshot right to left overlay placement 1`] = `
|
|
||||||
<div>
|
|
||||||
<OverlayTrigger
|
|
||||||
key="left-basic"
|
|
||||||
overlay={
|
|
||||||
<Tooltip
|
|
||||||
id="course-grade-tooltip"
|
|
||||||
>
|
|
||||||
Total Grade values are always displayed as a percentage
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
placement="left"
|
|
||||||
trigger={
|
|
||||||
[
|
|
||||||
"hover",
|
|
||||||
"focus",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
Total Grade (%)
|
|
||||||
<div
|
|
||||||
id="courseGradeTooltipIcon"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
className="fa fa-info-circle"
|
|
||||||
screenReaderText="Total Grade values are always displayed as a percentage"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</OverlayTrigger>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`GradebookTable snapshot 1`] = `
|
|
||||||
<div
|
|
||||||
className="gradebook-container"
|
|
||||||
>
|
|
||||||
<DataTable
|
|
||||||
RowStatusComponent={[MockFunction hooks.nullMethod]}
|
|
||||||
columns={
|
|
||||||
[
|
|
||||||
"some",
|
|
||||||
"columns",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
data={
|
|
||||||
[
|
|
||||||
"some",
|
|
||||||
"data",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
hasFixedColumnWidths={true}
|
|
||||||
itemCount={3}
|
|
||||||
rowHeaderColumnKey="username"
|
|
||||||
>
|
|
||||||
<DataTable.TableControlBar />
|
|
||||||
<DataTable.Table />
|
|
||||||
<DataTable.EmptyTable
|
|
||||||
content="empty-table-content"
|
|
||||||
/>
|
|
||||||
</DataTable>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,47 +1,32 @@
|
|||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import React from 'react';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
|
||||||
|
|
||||||
import { formatMessage } from 'testUtils';
|
|
||||||
|
|
||||||
import { getLocalizedPercentSign } from 'i18n/utils';
|
|
||||||
import { selectors } from 'data/redux/hooks';
|
|
||||||
import transforms from 'data/redux/transforms';
|
|
||||||
import { Headings } from 'data/constants/grades';
|
import { Headings } from 'data/constants/grades';
|
||||||
import LabelReplacements from './LabelReplacements';
|
|
||||||
import Fields from './Fields';
|
|
||||||
import GradeButton from './GradeButton';
|
|
||||||
|
|
||||||
|
import { initializeMocks, render } from '../../../testUtilsExtra';
|
||||||
|
import * as hooks from './hooks';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
import useGradebookTableData from './hooks';
|
let mockUseAllGrades;
|
||||||
|
let mockUseGetHeadings;
|
||||||
jest.mock('i18n/utils', () => ({
|
|
||||||
getLocalizedPercentSign: () => '%',
|
|
||||||
}));
|
|
||||||
jest.mock('./GradeButton', () => 'GradeButton');
|
|
||||||
jest.mock('./Fields', () => jest.requireActual('testUtils').mockNestedComponents({
|
|
||||||
Username: 'Fields.Username',
|
|
||||||
Text: 'Fields.Text',
|
|
||||||
}));
|
|
||||||
jest.mock('./LabelReplacements', () => jest.requireActual('testUtils').mockNestedComponents({
|
|
||||||
TotalGradeLabelReplacement: 'LabelReplacements.TotalGradeLabelReplacement',
|
|
||||||
UsernameLabelReplacement: 'LabelReplacements.UsernameLabelReplacement',
|
|
||||||
MastersOnlyLabelReplacement: 'LabelReplacements.MastersOnlyLabelReplacement',
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('data/redux/hooks', () => ({
|
jest.mock('data/redux/hooks', () => ({
|
||||||
selectors: {
|
selectors: {
|
||||||
grades: { useAllGrades: jest.fn() },
|
grades: { useAllGrades: () => mockUseAllGrades() },
|
||||||
root: { useGetHeadings: jest.fn() },
|
root: { useGetHeadings: () => mockUseGetHeadings() },
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('data/redux/transforms', () => ({
|
jest.mock('data/redux/transforms', () => ({
|
||||||
grades: { roundGrade: jest.fn() },
|
grades: { roundGrade: jest.fn((val) => val) },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const roundGrade = grade => grade * 20;
|
jest.mock('i18n/utils', () => ({ getLocalizedPercentSign: () => '%' }));
|
||||||
transforms.grades.roundGrade.mockImplementation(roundGrade);
|
jest.mock('./Fields', () => ({ Username: () => null, Text: () => null }));
|
||||||
|
jest.mock('./GradeButton', () => ({ __esModule: true, default: () => null }));
|
||||||
|
jest.mock('./LabelReplacements', () => ({
|
||||||
|
TotalGradeLabelReplacement: () => null,
|
||||||
|
UsernameLabelReplacement: () => null,
|
||||||
|
MastersOnlyLabelReplacement: () => null,
|
||||||
|
}));
|
||||||
|
|
||||||
const subsectionLabels = [
|
const subsectionLabels = [
|
||||||
'subsectionLabel1',
|
'subsectionLabel1',
|
||||||
@@ -85,7 +70,9 @@ const allGrades = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const testHeading = 'test-heading-value';
|
const testHeading = 'test-heading-value';
|
||||||
|
|
||||||
const headings = [
|
const headings = [
|
||||||
Headings.totalGrade,
|
Headings.totalGrade,
|
||||||
Headings.username,
|
Headings.username,
|
||||||
@@ -93,100 +80,60 @@ const headings = [
|
|||||||
Headings.fullName,
|
Headings.fullName,
|
||||||
testHeading,
|
testHeading,
|
||||||
];
|
];
|
||||||
selectors.grades.useAllGrades.mockReturnValue(allGrades);
|
|
||||||
selectors.root.useGetHeadings.mockReturnValue(headings);
|
|
||||||
|
|
||||||
let out;
|
describe('useGradebookTableData hook', () => {
|
||||||
describe('useGradebookTableData', () => {
|
beforeAll(() => {
|
||||||
|
mockUseAllGrades = jest.fn();
|
||||||
|
mockUseGetHeadings = jest.fn();
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
mockUseAllGrades.mockReset();
|
||||||
out = useGradebookTableData();
|
mockUseGetHeadings.mockReset();
|
||||||
});
|
});
|
||||||
describe('behavior', () => {
|
let hookResult;
|
||||||
it('initializes intl hook', () => {
|
|
||||||
expect(useIntl).toHaveBeenCalled();
|
const TestComponent = () => {
|
||||||
});
|
hookResult = hooks.useGradebookTableData();
|
||||||
it('initializes redux hooks', () => {
|
return null;
|
||||||
expect(selectors.grades.useAllGrades).toHaveBeenCalled();
|
};
|
||||||
expect(selectors.root.useGetHeadings).toHaveBeenCalled();
|
|
||||||
|
beforeEach(() => {
|
||||||
|
initializeMocks();
|
||||||
|
hookResult = null;
|
||||||
|
mockUseAllGrades.mockReturnValue([]);
|
||||||
|
mockUseGetHeadings.mockReturnValue([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns expected structure with empty data', () => {
|
||||||
|
render(<TestComponent />);
|
||||||
|
expect(hookResult).toEqual({
|
||||||
|
columns: [],
|
||||||
|
data: [],
|
||||||
|
grades: [],
|
||||||
|
nullMethod: expect.any(Function),
|
||||||
|
emptyContent: expect.any(String),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('output', () => {
|
|
||||||
describe('columns', () => {
|
it('nullMethod returns null', () => {
|
||||||
test('total grade heading produces TotalGradeLabelReplacement label', () => {
|
render(<TestComponent />);
|
||||||
const { Header, accessor } = out.columns[0];
|
expect(hookResult.nullMethod()).toBeNull();
|
||||||
expect(accessor).toEqual(headings[0]);
|
});
|
||||||
expect(shallow(Header)).toMatchObject(
|
|
||||||
shallow(<LabelReplacements.TotalGradeLabelReplacement />),
|
it('returns expected structure with grades and headings data', () => {
|
||||||
);
|
mockUseAllGrades.mockReturnValue(allGrades);
|
||||||
});
|
mockUseGetHeadings.mockReturnValue(headings);
|
||||||
test('username heading produces UsernameLabelReplacement', () => {
|
render(<TestComponent />);
|
||||||
const { Header, accessor } = out.columns[1];
|
expect(hookResult.columns.length).toBe(headings.length);
|
||||||
expect(accessor).toEqual(headings[1]);
|
expect(hookResult.columns[0].accessor).toEqual(headings[0]);
|
||||||
expect(shallow(Header)).toMatchObject(
|
expect(hookResult.data.length).toBe(allGrades.length);
|
||||||
shallow(<LabelReplacements.UsernameLabelReplacement />),
|
expect(hookResult.data[0]).toHaveProperty(Headings.username);
|
||||||
);
|
expect(hookResult.grades).toEqual(allGrades);
|
||||||
});
|
expect(hookResult.nullMethod()).toBeNull();
|
||||||
test('email heading replaces with email heading message', () => {
|
expect(hookResult.emptyContent).toBe(messages.noResultsFound.defaultMessage);
|
||||||
const { Header, accessor } = out.columns[2];
|
|
||||||
expect(accessor).toEqual(headings[2]);
|
expect(mockUseAllGrades).toHaveBeenCalled();
|
||||||
expect(shallow(Header)).toMatchObject(
|
expect(mockUseGetHeadings).toHaveBeenCalled();
|
||||||
shallow(<LabelReplacements.MastersOnlyLabelReplacement {...messages.emailHeading} />),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
test('fullName heading replaces with fullName heading message', () => {
|
|
||||||
const { Header, accessor } = out.columns[3];
|
|
||||||
expect(accessor).toEqual(headings[3]);
|
|
||||||
expect(shallow(Header)).toMatchObject(
|
|
||||||
shallow(<LabelReplacements.MastersOnlyLabelReplacement {...messages.fullNameHeading} />),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
test('other headings are passed through', () => {
|
|
||||||
const { Header, accessor } = out.columns[4];
|
|
||||||
expect(accessor).toEqual(headings[4]);
|
|
||||||
expect(Header).toEqual(headings[4]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('data', () => {
|
|
||||||
test('username field', () => {
|
|
||||||
allGrades.forEach((entry, index) => {
|
|
||||||
expect(out.data[index][Headings.username]).toMatchObject(
|
|
||||||
<Fields.Username username={entry.username} userKey={entry.external_user_key} />,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
test('email field', () => {
|
|
||||||
allGrades.forEach((entry, index) => {
|
|
||||||
expect(out.data[index][Headings.email]).toMatchObject(
|
|
||||||
<Fields.Text value={entry.email} />,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
test('totalGrade field', () => {
|
|
||||||
allGrades.forEach((entry, index) => {
|
|
||||||
expect(out.data[index][Headings.totalGrade]).toEqual(
|
|
||||||
`${roundGrade(entry.percent * 100)}${getLocalizedPercentSign()}`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
test('section breakdown', () => {
|
|
||||||
allGrades.forEach((entry, gradeIndex) => {
|
|
||||||
subsectionLabels.forEach((label, labelIndex) => {
|
|
||||||
expect(out.data[gradeIndex][label]).toMatchObject(
|
|
||||||
<GradeButton entry={entry} subsection={entry.section_breakdown[labelIndex]} />,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('forwards grades from redux', () => {
|
|
||||||
expect(out.grades).toEqual(allGrades);
|
|
||||||
});
|
|
||||||
test('nullMethod returns null', () => {
|
|
||||||
expect(out.nullMethod()).toEqual(null);
|
|
||||||
});
|
|
||||||
test('emptyContent', () => {
|
|
||||||
expect(out.emptyContent).toEqual(formatMessage(messages.noResultsFound));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,39 +1,42 @@
|
|||||||
import React from 'react';
|
import { screen } from '@testing-library/react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
|
||||||
|
|
||||||
import { DataTable } from '@openedx/paragon';
|
|
||||||
|
|
||||||
import useGradebookTableData from './hooks';
|
import useGradebookTableData from './hooks';
|
||||||
import GradebookTable from '.';
|
import GradebookTable from '.';
|
||||||
|
import { renderWithIntl } from '../../../testUtilsExtra';
|
||||||
|
|
||||||
jest.mock('./hooks', () => jest.fn());
|
jest.mock('./hooks', () => jest.fn());
|
||||||
|
|
||||||
const hookProps = {
|
const hookProps = {
|
||||||
columns: ['some', 'columns'],
|
columns: [{ Header: 'Username', accessor: 'username' }, { Header: 'Email', accessor: 'email' }, { Header: 'Total Grade', accessor: 'totalGrade' }],
|
||||||
data: ['some', 'data'],
|
data: [{ username: 'instructor', email: 'instructor@example.com', totalGrade: '100' }, { username: 'student', email: 'student@example.com', totalGrade: '90' }],
|
||||||
grades: ['a', 'few', 'grades'],
|
grades: ['a', 'few', 'grades'],
|
||||||
nullMethod: jest.fn().mockName('hooks.nullMethod'),
|
nullMethod: jest.fn().mockName('hooks.nullMethod'),
|
||||||
emptyContent: 'empty-table-content',
|
emptyContent: 'empty-table-content',
|
||||||
};
|
};
|
||||||
useGradebookTableData.mockReturnValue(hookProps);
|
|
||||||
|
|
||||||
let el;
|
|
||||||
describe('GradebookTable', () => {
|
describe('GradebookTable', () => {
|
||||||
beforeEach(() => {
|
it('renders Datatable correctly', () => {
|
||||||
jest.clearAllMocks();
|
useGradebookTableData.mockReturnValue(hookProps);
|
||||||
el = shallow(<GradebookTable />);
|
renderWithIntl(<GradebookTable />);
|
||||||
|
expect(useGradebookTableData).toHaveBeenCalled();
|
||||||
|
const headers = screen.getAllByRole('columnheader');
|
||||||
|
expect(headers).toHaveLength(3);
|
||||||
|
expect(headers[0]).toHaveTextContent(hookProps.columns[0].Header);
|
||||||
|
expect(headers[1]).toHaveTextContent(hookProps.columns[1].Header);
|
||||||
|
expect(headers[2]).toHaveTextContent(hookProps.columns[2].Header);
|
||||||
|
const rows = screen.getAllByRole('row');
|
||||||
|
expect(rows).toHaveLength(3);
|
||||||
|
expect(screen.getByText(hookProps.data[0].username)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(hookProps.data[0].email)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(hookProps.data[0].totalGrade)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
test('snapshot', () => {
|
it('renders empty table content when no data is available', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
useGradebookTableData.mockReturnValue({
|
||||||
});
|
...hookProps,
|
||||||
test('Datatable props', () => {
|
data: [],
|
||||||
const datatable = el.instance.findByType(DataTable)[0];
|
grades: [],
|
||||||
const { props } = datatable;
|
});
|
||||||
expect(props.columns).toEqual(hookProps.columns);
|
renderWithIntl(<GradebookTable />);
|
||||||
expect(props.data).toEqual(hookProps.data);
|
expect(screen.getByText(hookProps.emptyContent)).toBeInTheDocument();
|
||||||
expect(props.itemCount).toEqual(hookProps.grades.length);
|
|
||||||
expect(props.RowStatusComponent).toEqual(hookProps.nullMethod);
|
|
||||||
expect(datatable.children[2].type).toEqual('DataTable.EmptyTable');
|
|
||||||
expect(datatable.children[2].props.content).toEqual(hookProps.emptyContent);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -120,25 +120,25 @@ select#ScoreView.form-control {
|
|||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
border-width: 0.4rem 0.4rem 0.4rem 0;
|
border-width: 0.4rem 0.4rem 0.4rem 0;
|
||||||
border-right-color: $black;
|
border-right-color: var(--pgn-color-black);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#edit-filters-btn {
|
#edit-filters-btn {
|
||||||
@include media-breakpoint-down(xs) {
|
@media (--pgn-size-breakpoint-max-width-xs) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-container {
|
.search-container {
|
||||||
@include media-breakpoint-down(xs) {
|
@media (--pgn-size-breakpoint-max-width-xs) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pgn__modal-body-content .pgn__data-table-layout-wrapper {
|
.pgn__modal-body-content .pgn__data-table-layout-wrapper {
|
||||||
@include media-breakpoint-down(sm) {
|
@media (--pgn-size-breakpoint-max-width-xs) {
|
||||||
clear: both;
|
clear: both;
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
// 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
|
|
||||||
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>
|
|
||||||
<NetworkButton
|
|
||||||
className="import-grades-btn"
|
|
||||||
import={true}
|
|
||||||
label={
|
|
||||||
{
|
|
||||||
"defaultMessage": "Import Grades",
|
|
||||||
"description": "A labeled button to import grades in the BulkManagement Tab File Upload Form",
|
|
||||||
"id": "gradebook.GradesView.importGradesBtnText",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onClick={[MockFunction props.handleClickImportGrades]}
|
|
||||||
/>
|
|
||||||
</Fragment>
|
|
||||||
`;
|
|
||||||
@@ -12,6 +12,11 @@ jest.mock('data/redux/hooks', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('react', () => ({
|
||||||
|
...jest.requireActual('react'),
|
||||||
|
useRef: jest.fn((val) => ({ current: val, useRef: true })),
|
||||||
|
}));
|
||||||
|
|
||||||
let out;
|
let out;
|
||||||
|
|
||||||
let submitThen;
|
let submitThen;
|
||||||
|
|||||||
@@ -1,46 +1,26 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import {
|
||||||
import { Form } from '@openedx/paragon';
|
render, screen, initializeMocks,
|
||||||
|
} from 'testUtilsExtra';
|
||||||
|
|
||||||
import NetworkButton from 'components/NetworkButton';
|
|
||||||
import useImportGradesButtonData from './hooks';
|
|
||||||
import ImportGradesButton from '.';
|
import ImportGradesButton from '.';
|
||||||
|
|
||||||
jest.mock('components/NetworkButton', () => 'NetworkButton');
|
initializeMocks();
|
||||||
jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
|
|
||||||
|
|
||||||
let el;
|
|
||||||
let props;
|
|
||||||
describe('ImportGradesButton component', () => {
|
describe('ImportGradesButton component', () => {
|
||||||
beforeAll(() => {
|
beforeEach(() => {
|
||||||
props = {
|
jest.clearAllMocks();
|
||||||
fileInputRef: { current: null },
|
render(<ImportGradesButton />);
|
||||||
gradeExportUrl: 'test-grade-export-url',
|
|
||||||
handleClickImportGrades: jest.fn().mockName('props.handleClickImportGrades'),
|
|
||||||
handleFileInputChange: jest.fn().mockName('props.handleFileInputChange'),
|
|
||||||
};
|
|
||||||
useImportGradesButtonData.mockReturnValue(props);
|
|
||||||
el = shallow(<ImportGradesButton />);
|
|
||||||
});
|
|
||||||
describe('behavior', () => {
|
|
||||||
it('initializes hooks', () => {
|
|
||||||
expect(useImportGradesButtonData).toHaveBeenCalledWith();
|
|
||||||
expect(useIntl).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
test('snapshot', () => {
|
test('Form', async () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
const uploader = screen.getByTestId('file-control');
|
||||||
});
|
expect(uploader).toBeInTheDocument();
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
test('import button', () => {
|
test('import button', () => {
|
||||||
expect(el.instance.findByType(NetworkButton)[0].props.onClick).toEqual(props.handleClickImportGrades);
|
expect(screen.getByRole('button', { name: 'Import Grades' })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from '@testing-library/react';
|
|
||||||
|
|
||||||
import useImportGradesButtonData from './hooks';
|
import useImportGradesButtonData from './hooks';
|
||||||
import ImportGradesButton from '.';
|
import ImportGradesButton from '.';
|
||||||
|
import { renderWithIntl, screen } from '../../../testUtilsExtra';
|
||||||
|
|
||||||
jest.unmock('react');
|
|
||||||
jest.unmock('@openedx/paragon');
|
|
||||||
jest.mock('components/NetworkButton', () => 'network-button');
|
jest.mock('components/NetworkButton', () => 'network-button');
|
||||||
jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
|
jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
|
||||||
|
|
||||||
@@ -17,11 +14,10 @@ const props = {
|
|||||||
};
|
};
|
||||||
useImportGradesButtonData.mockReturnValue(props);
|
useImportGradesButtonData.mockReturnValue(props);
|
||||||
|
|
||||||
let el;
|
|
||||||
describe('ImportGradesButton ref test', () => {
|
describe('ImportGradesButton ref test', () => {
|
||||||
it('loads ref from hook', () => {
|
it('loads ref from hook', () => {
|
||||||
el = render(<ImportGradesButton />);
|
renderWithIntl(<ImportGradesButton />);
|
||||||
const input = el.getByTestId('file-control');
|
const input = screen.getByTestId('file-control');
|
||||||
expect(input).toEqual(props.fileInputRef.current);
|
expect(input).toEqual(props.fileInputRef.current);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`ImportSuccessToast component render snapshot 1`] = `
|
|
||||||
<Toast
|
|
||||||
action="test-action"
|
|
||||||
onClose={[MockFunction hooks.onClose]}
|
|
||||||
show="test-show"
|
|
||||||
>
|
|
||||||
test-description
|
|
||||||
</Toast>
|
|
||||||
`;
|
|
||||||
@@ -19,6 +19,18 @@ jest.mock('data/redux/hooks', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('react', () => ({
|
||||||
|
...jest.requireActual('react'),
|
||||||
|
useContext: jest.fn(context => context),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform/i18n', () => ({
|
||||||
|
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||||
|
useIntl: jest.fn(() => ({
|
||||||
|
formatMessage: (message) => message.defaultMessage,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
const setView = jest.fn().mockName('hooks.setView');
|
const setView = jest.fn().mockName('hooks.setView');
|
||||||
const setShowToast = jest.fn().mockName('hooks.setShowImportSuccessToast');
|
const setShowToast = jest.fn().mockName('hooks.setShowImportSuccessToast');
|
||||||
actions.app.useSetView.mockReturnValue(setView);
|
actions.app.useSetView.mockReturnValue(setView);
|
||||||
|
|||||||
@@ -1,39 +1,88 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
|
||||||
|
|
||||||
import useImportSuccessToastData from './hooks';
|
import { render, initializeMocks, screen } from 'testUtilsExtra';
|
||||||
|
|
||||||
import ImportSuccessToast from '.';
|
import ImportSuccessToast from '.';
|
||||||
|
import useImportSuccessToastData from './hooks';
|
||||||
|
|
||||||
|
jest.mock('data/redux/hooks', () => ({
|
||||||
|
actions: {
|
||||||
|
app: {
|
||||||
|
useSetView: jest.fn(),
|
||||||
|
useSetShowImportSuccessToast: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
app: {
|
||||||
|
useShowImportSuccessToast: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock('./hooks', () => jest.fn());
|
jest.mock('./hooks', () => jest.fn());
|
||||||
|
|
||||||
const hookProps = {
|
initializeMocks();
|
||||||
action: 'test-action',
|
|
||||||
onClose: jest.fn().mockName('hooks.onClose'),
|
|
||||||
show: 'test-show',
|
|
||||||
description: 'test-description',
|
|
||||||
};
|
|
||||||
useImportSuccessToastData.mockReturnValue(hookProps);
|
|
||||||
|
|
||||||
let el;
|
describe('ImportSuccessToast', () => {
|
||||||
describe('ImportSuccessToast component', () => {
|
beforeEach(() => {
|
||||||
beforeAll(() => {
|
jest.clearAllMocks();
|
||||||
el = shallow(<ImportSuccessToast />);
|
|
||||||
});
|
});
|
||||||
describe('behavior', () => {
|
|
||||||
it('initializes component hook', () => {
|
it('renders with show false', () => {
|
||||||
expect(useImportSuccessToastData).toHaveBeenCalled();
|
useImportSuccessToastData.mockReturnValue({
|
||||||
|
action: {
|
||||||
|
label: 'View Activity Log',
|
||||||
|
onClick: jest.fn(),
|
||||||
|
},
|
||||||
|
onClose: jest.fn(),
|
||||||
|
show: false,
|
||||||
|
description: 'Import Successful! Grades will be updated momentarily.',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
render(<ImportSuccessToast />);
|
||||||
|
|
||||||
|
const toastRoot = document.getElementById('toast-root');
|
||||||
|
expect(toastRoot).toBeInTheDocument();
|
||||||
|
expect(toastRoot).toHaveClass('toast-container');
|
||||||
|
|
||||||
|
const toastMessage = screen.queryByText('Import Successful! Grades will be updated momentarily.');
|
||||||
|
expect(toastMessage).toBeNull();
|
||||||
|
expect(useImportSuccessToastData).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
describe('render', () => {
|
|
||||||
test('snapshot', () => {
|
it('renders with show true', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
useImportSuccessToastData.mockReturnValue({
|
||||||
|
action: {
|
||||||
|
label: 'View Activity Log',
|
||||||
|
onClick: jest.fn(),
|
||||||
|
},
|
||||||
|
onClose: jest.fn(),
|
||||||
|
show: true,
|
||||||
|
description: 'Import Successful! Grades will be updated momentarily.',
|
||||||
});
|
});
|
||||||
test('Toast', () => {
|
|
||||||
expect(el.instance.type).toEqual('Toast');
|
render(<ImportSuccessToast />);
|
||||||
expect(el.instance.props.action).toEqual(hookProps.action);
|
const toastMessage = screen.getByText('Import Successful! Grades will be updated momentarily.');
|
||||||
expect(el.instance.props.onClose).toEqual(hookProps.onClose);
|
expect(toastMessage).toBeInTheDocument();
|
||||||
expect(el.instance.props.show).toEqual(hookProps.show);
|
expect(useImportSuccessToastData).toHaveBeenCalled();
|
||||||
expect(el.instance.children[0].el).toEqual(hookProps.description);
|
});
|
||||||
|
|
||||||
|
it('passes correct props to Toast component', () => {
|
||||||
|
const mockOnClose = jest.fn();
|
||||||
|
const mockOnClick = jest.fn();
|
||||||
|
|
||||||
|
useImportSuccessToastData.mockReturnValue({
|
||||||
|
action: {
|
||||||
|
label: 'View Activity Log',
|
||||||
|
onClick: mockOnClick,
|
||||||
|
},
|
||||||
|
onClose: mockOnClose,
|
||||||
|
show: true,
|
||||||
|
description: 'Import Successful! Grades will be updated momentarily.',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { container } = render(<ImportSuccessToast />);
|
||||||
|
expect(container).toBeInTheDocument();
|
||||||
|
expect(useImportSuccessToastData).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`InterventionsReport component output snapshot 1`] = `
|
|
||||||
<div>
|
|
||||||
<h4
|
|
||||||
className="mt-0"
|
|
||||||
>
|
|
||||||
Interventions Report
|
|
||||||
</h4>
|
|
||||||
<div
|
|
||||||
className="d-flex justify-content-between align-items-center"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="intervention-report-description"
|
|
||||||
>
|
|
||||||
Need to find students who may be falling behind? Download the interventions report to obtain engagement metrics such as section attempts and visits.
|
|
||||||
</div>
|
|
||||||
<NetworkButton
|
|
||||||
label={
|
|
||||||
{
|
|
||||||
"defaultMessage": "Download Interventions",
|
|
||||||
"description": "The labeled button to download the Intervention report from the Grades View",
|
|
||||||
"id": "gradebook.GradesView.InterventionsReport.downloadBtn",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onClick={[MockFunction]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,42 +1,73 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { render, screen, initializeMocks } from 'testUtilsExtra';
|
||||||
|
|
||||||
import NetworkButton from 'components/NetworkButton';
|
|
||||||
|
|
||||||
import messages from './messages';
|
|
||||||
import useInterventionsReportData from './hooks';
|
|
||||||
import InterventionsReport from '.';
|
import InterventionsReport from '.';
|
||||||
|
|
||||||
jest.mock('components/NetworkButton', () => 'NetworkButton');
|
jest.mock('components/NetworkButton', () => 'NetworkButton');
|
||||||
jest.mock('./hooks', () => jest.fn());
|
jest.mock('./hooks', () => jest.fn());
|
||||||
|
|
||||||
const hookProps = { show: true, handleClick: jest.fn() };
|
const useInterventionsReportData = require('./hooks');
|
||||||
useInterventionsReportData.mockReturnValue(hookProps);
|
|
||||||
|
|
||||||
let el;
|
initializeMocks();
|
||||||
describe('InterventionsReport component', () => {
|
|
||||||
|
describe('InterventionsReport', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
el = shallow(<InterventionsReport />);
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
describe('behavior', () => {
|
|
||||||
it('initializes hooks', () => {
|
it('renders without errors when show is true', () => {
|
||||||
expect(useInterventionsReportData).toHaveBeenCalledWith();
|
useInterventionsReportData.mockReturnValue({
|
||||||
expect(useIntl).toHaveBeenCalledWith();
|
show: true,
|
||||||
|
handleClick: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
render(<InterventionsReport />);
|
||||||
|
expect(screen.getByRole('heading', { level: 4 })).toBeInTheDocument();
|
||||||
|
expect(useInterventionsReportData).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
describe('output', () => {
|
|
||||||
it('does now render if show is false', () => {
|
it('renders nothing when show is false', () => {
|
||||||
useInterventionsReportData.mockReturnValueOnce({ ...hookProps, show: false });
|
useInterventionsReportData.mockReturnValue({
|
||||||
el = shallow(<InterventionsReport />);
|
show: false,
|
||||||
expect(el.isEmptyRender()).toEqual(true);
|
handleClick: jest.fn(),
|
||||||
});
|
});
|
||||||
test('snapshot', () => {
|
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
render(<InterventionsReport />);
|
||||||
const btnProps = el.instance.findByType(NetworkButton)[0].props;
|
expect(screen.queryByRole('heading')).not.toBeInTheDocument();
|
||||||
expect(btnProps.label).toEqual(messages.downloadBtn);
|
expect(screen.queryByText('Interventions Report')).not.toBeInTheDocument();
|
||||||
expect(btnProps.onClick).toEqual(hookProps.handleClick);
|
expect(useInterventionsReportData).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls useInterventionsReportData hook', () => {
|
||||||
|
useInterventionsReportData.mockReturnValue({
|
||||||
|
show: true,
|
||||||
|
handleClick: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
render(<InterventionsReport />);
|
||||||
|
expect(useInterventionsReportData).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with correct content when show is true', () => {
|
||||||
|
const mockReportData = {
|
||||||
|
show: true,
|
||||||
|
handleClick: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
useInterventionsReportData.mockReturnValue(mockReportData);
|
||||||
|
|
||||||
|
render(<InterventionsReport />);
|
||||||
|
|
||||||
|
expect(screen.getByText('Interventions Report')).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByText(/Need to find students who may be falling behind/),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
const networkButton = document.querySelector('networkbutton');
|
||||||
|
expect(networkButton).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(useInterventionsReportData).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`PageButtons component render snapshot 1`] = `
|
|
||||||
<div
|
|
||||||
className="d-flex justify-content-center"
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"paddingBottom": "20px",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
disabled="prev-disabled"
|
|
||||||
onClick={[MockFunction hooks.prev.onClick]}
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"margin": "20px",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
variant="outline-primary"
|
|
||||||
>
|
|
||||||
prev-text
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
disabled="next-disabled"
|
|
||||||
onClick={[MockFunction hooks.next.onClick]}
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"margin": "20px",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
variant="outline-primary"
|
|
||||||
>
|
|
||||||
next-text
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -15,6 +15,18 @@ jest.mock('data/redux/hooks', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('react', () => ({
|
||||||
|
...jest.requireActual('react'),
|
||||||
|
useContext: jest.fn(context => context),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform/i18n', () => ({
|
||||||
|
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||||
|
useIntl: jest.fn(() => ({
|
||||||
|
formatMessage: (message) => message.defaultMessage,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
const gradeData = { nextPage: 'test-next-page', prevPage: 'test-prev-page' };
|
const gradeData = { nextPage: 'test-next-page', prevPage: 'test-prev-page' };
|
||||||
selectors.grades.useGradeData.mockReturnValue(gradeData);
|
selectors.grades.useGradeData.mockReturnValue(gradeData);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
|
||||||
|
|
||||||
import { Button } from '@openedx/paragon';
|
import { initializeMocks, render, screen } from 'testUtilsExtra';
|
||||||
|
|
||||||
import usePageButtonsData from './hooks';
|
import usePageButtonsData from './hooks';
|
||||||
import PageButtons from '.';
|
import PageButtons from '.';
|
||||||
@@ -10,44 +9,53 @@ jest.mock('./hooks', () => jest.fn());
|
|||||||
|
|
||||||
const hookProps = {
|
const hookProps = {
|
||||||
prev: {
|
prev: {
|
||||||
disabled: 'prev-disabled',
|
disabled: false,
|
||||||
onClick: jest.fn().mockName('hooks.prev.onClick'),
|
onClick: jest.fn().mockName('hooks.prev.onClick'),
|
||||||
text: 'prev-text',
|
text: 'prev-text',
|
||||||
},
|
},
|
||||||
next: {
|
next: {
|
||||||
disabled: 'next-disabled',
|
disabled: false,
|
||||||
onClick: jest.fn().mockName('hooks.next.onClick'),
|
onClick: jest.fn().mockName('hooks.next.onClick'),
|
||||||
text: 'next-text',
|
text: 'next-text',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
usePageButtonsData.mockReturnValue(hookProps);
|
|
||||||
|
|
||||||
let el;
|
|
||||||
describe('PageButtons component', () => {
|
describe('PageButtons component', () => {
|
||||||
beforeEach(() => {
|
beforeAll(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
el = shallow(<PageButtons />);
|
initializeMocks();
|
||||||
});
|
});
|
||||||
describe('behavior', () => {
|
describe('renders enabled buttons', () => {
|
||||||
it('initializes component hooks', () => {
|
beforeEach(() => {
|
||||||
expect(usePageButtonsData).toHaveBeenCalled();
|
usePageButtonsData.mockReturnValue(hookProps);
|
||||||
|
render(<PageButtons />);
|
||||||
|
});
|
||||||
|
test('prev button enabled', () => {
|
||||||
|
expect(screen.getByText(hookProps.prev.text)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(hookProps.next.text)).toBeEnabled();
|
||||||
|
});
|
||||||
|
test('next button enabled', () => {
|
||||||
|
expect(screen.getByText(hookProps.next.text)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(hookProps.prev.text)).toBeEnabled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('render', () => {
|
|
||||||
test('snapshot', () => {
|
describe('renders disabled buttons', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
beforeAll(() => {
|
||||||
|
hookProps.prev.disabled = true;
|
||||||
|
hookProps.next.disabled = true;
|
||||||
});
|
});
|
||||||
test('prev button', () => {
|
beforeEach(() => {
|
||||||
const button = el.instance.findByType(Button)[0];
|
usePageButtonsData.mockReturnValue(hookProps);
|
||||||
expect(button.props.disabled).toEqual(hookProps.prev.disabled);
|
render(<PageButtons />);
|
||||||
expect(button.props.onClick).toEqual(hookProps.prev.onClick);
|
|
||||||
expect(button.children[0].el).toEqual(hookProps.prev.text);
|
|
||||||
});
|
});
|
||||||
test('next button', () => {
|
test('prev button disabled', () => {
|
||||||
const button = el.instance.findByType(Button)[1];
|
expect(screen.getByText(hookProps.next.text)).toBeInTheDocument();
|
||||||
expect(button.props.disabled).toEqual(hookProps.next.disabled);
|
expect(screen.getByText(hookProps.prev.text)).toBeDisabled();
|
||||||
expect(button.props.onClick).toEqual(hookProps.next.onClick);
|
});
|
||||||
expect(button.children[0].el).toEqual(hookProps.next.text);
|
test('next button disabled', () => {
|
||||||
|
expect(screen.getByText(hookProps.prev.text)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(hookProps.next.text)).toBeDisabled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`ScoreViewInput component render snapshot 1`] = `
|
|
||||||
<Form.Group
|
|
||||||
controlId="ScoreView"
|
|
||||||
>
|
|
||||||
<Form.Label>
|
|
||||||
Score View
|
|
||||||
:
|
|
||||||
</Form.Label>
|
|
||||||
<Form.Control
|
|
||||||
as="select"
|
|
||||||
onChange={[MockFunction hooks.toggleGradeFormat]}
|
|
||||||
value="test-grade-format"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
value="percent"
|
|
||||||
>
|
|
||||||
Percent
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
value="absolute"
|
|
||||||
>
|
|
||||||
Absolute
|
|
||||||
</option>
|
|
||||||
</Form.Control>
|
|
||||||
</Form.Group>
|
|
||||||
`;
|
|
||||||
@@ -1,67 +1,237 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { render, screen, initializeMocks } from 'testUtilsExtra';
|
||||||
import { GradeFormats } from 'data/constants/grades';
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
import { formatMessage } from 'testUtils';
|
import { ScoreViewInput } from '.';
|
||||||
import { actions, selectors } from 'data/redux/hooks';
|
|
||||||
import ScoreViewInput from '.';
|
|
||||||
import messages from './messages';
|
|
||||||
|
|
||||||
jest.mock('data/redux/hooks', () => ({
|
jest.mock('data/redux/hooks', () => ({
|
||||||
actions: {
|
actions: {
|
||||||
grades: { useToggleGradeFormat: jest.fn() },
|
grades: {
|
||||||
|
useToggleGradeFormat: jest.fn(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
selectors: {
|
selectors: {
|
||||||
grades: { useGradeData: jest.fn() },
|
grades: {
|
||||||
|
useGradeData: jest.fn(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const toggleGradeFormat = jest.fn().mockName('hooks.toggleGradeFormat');
|
const { actions, selectors } = require('data/redux/hooks');
|
||||||
actions.grades.useToggleGradeFormat.mockReturnValue(toggleGradeFormat);
|
|
||||||
const gradeFormat = 'test-grade-format';
|
initializeMocks();
|
||||||
selectors.grades.useGradeData.mockReturnValue({ gradeFormat });
|
|
||||||
|
describe('ScoreViewInput', () => {
|
||||||
|
const mockToggleFormat = jest.fn();
|
||||||
|
|
||||||
let el;
|
|
||||||
describe('ScoreViewInput component', () => {
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
el = shallow(<ScoreViewInput />);
|
|
||||||
});
|
selectors.grades.useGradeData.mockReturnValue({
|
||||||
describe('behavior', () => {
|
gradeFormat: 'percent',
|
||||||
it('initializes intl hook', () => {
|
|
||||||
expect(useIntl).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
it('initializes redux hooks', () => {
|
actions.grades.useToggleGradeFormat.mockReturnValue(mockToggleFormat);
|
||||||
expect(actions.grades.useToggleGradeFormat).toHaveBeenCalled();
|
});
|
||||||
expect(selectors.grades.useGradeData).toHaveBeenCalled();
|
|
||||||
|
it('renders without errors', () => {
|
||||||
|
render(<ScoreViewInput />);
|
||||||
|
|
||||||
|
expect(document.body).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders form group with correct label', () => {
|
||||||
|
render(<ScoreViewInput />);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText(/score view/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders select element with correct options', () => {
|
||||||
|
render(<ScoreViewInput />);
|
||||||
|
|
||||||
|
const select = screen.getByRole('combobox', { name: /score view/i });
|
||||||
|
expect(select).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole('option', { name: /percent/i }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole('option', { name: /absolute/i }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays correct selected value for percent format', () => {
|
||||||
|
selectors.grades.useGradeData.mockReturnValue({
|
||||||
|
gradeFormat: 'percent',
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<ScoreViewInput />);
|
||||||
|
|
||||||
|
const select = screen.getByRole('combobox', { name: /score view/i });
|
||||||
|
expect(select).toHaveValue('percent');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays correct selected value for absolute format', () => {
|
||||||
|
selectors.grades.useGradeData.mockReturnValue({
|
||||||
|
gradeFormat: 'absolute',
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<ScoreViewInput />);
|
||||||
|
|
||||||
|
const select = screen.getByRole('combobox', { name: /score view/i });
|
||||||
|
expect(select).toHaveValue('absolute');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls toggle function when selection changes', async () => {
|
||||||
|
render(<ScoreViewInput />);
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
const select = screen.getByRole('combobox', { name: /score view/i });
|
||||||
|
|
||||||
|
await user.selectOptions(select, 'absolute');
|
||||||
|
|
||||||
|
expect(mockToggleFormat).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('accessibility', () => {
|
||||||
|
it('has proper form structure', () => {
|
||||||
|
render(<ScoreViewInput />);
|
||||||
|
|
||||||
|
const select = screen.getByRole('combobox', { name: /score view/i });
|
||||||
|
const label = screen.getByText(/score view/i);
|
||||||
|
|
||||||
|
expect(select).toBeInTheDocument();
|
||||||
|
expect(label).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has accessible label association', () => {
|
||||||
|
render(<ScoreViewInput />);
|
||||||
|
|
||||||
|
const label = screen.getByText(/score view/i);
|
||||||
|
const select = screen.getByRole('combobox', { name: /score view/i });
|
||||||
|
|
||||||
|
expect(label).toBeInTheDocument();
|
||||||
|
expect(select).toHaveAccessibleName(/score view/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has correct control ID for accessibility', () => {
|
||||||
|
render(<ScoreViewInput />);
|
||||||
|
|
||||||
|
const select = screen.getByRole('combobox', { name: /score view/i });
|
||||||
|
expect(select).toHaveAttribute('id', 'ScoreView');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('render', () => {
|
|
||||||
test('snapshot', () => {
|
describe('form control behavior', () => {
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
it('renders as a select element', () => {
|
||||||
|
render(<ScoreViewInput />);
|
||||||
|
|
||||||
|
const select = screen.getByRole('combobox', { name: /score view/i });
|
||||||
|
expect(select.tagName).toBe('SELECT');
|
||||||
});
|
});
|
||||||
test('label', () => {
|
|
||||||
const label = el.instance.children[0];
|
it('has correct option values', () => {
|
||||||
expect(label.children[0].el).toEqual(`${formatMessage(messages.scoreView)}`);
|
render(<ScoreViewInput />);
|
||||||
|
|
||||||
|
const percentOption = screen.getByRole('option', { name: /percent/i });
|
||||||
|
const absoluteOption = screen.getByRole('option', { name: /absolute/i });
|
||||||
|
|
||||||
|
expect(percentOption).toHaveValue('percent');
|
||||||
|
expect(absoluteOption).toHaveValue('absolute');
|
||||||
});
|
});
|
||||||
describe('form control', () => {
|
|
||||||
let control;
|
it('has exactly two options', () => {
|
||||||
beforeEach(() => {
|
render(<ScoreViewInput />);
|
||||||
control = el.instance.children;
|
|
||||||
});
|
const options = screen.getAllByRole('option');
|
||||||
test('value and onChange from redux hooks', () => {
|
expect(options).toHaveLength(2);
|
||||||
expect(control[1].props.value).toEqual(gradeFormat);
|
});
|
||||||
expect(control[1].props.onChange).toEqual(toggleGradeFormat);
|
});
|
||||||
});
|
|
||||||
test('absolute and percent options', () => {
|
describe('redux integration', () => {
|
||||||
const { children } = control[1];
|
it('uses grade data selector hook', () => {
|
||||||
expect(children[0].props.value).toEqual(GradeFormats.percent);
|
render(<ScoreViewInput />);
|
||||||
expect(children[0].children[0].el).toEqual(formatMessage(messages.percent));
|
|
||||||
expect(children[1].props.value).toEqual(GradeFormats.absolute);
|
expect(selectors.grades.useGradeData).toHaveBeenCalledTimes(1);
|
||||||
expect(children[1].children[0].el).toEqual(formatMessage(messages.absolute));
|
});
|
||||||
|
|
||||||
|
it('uses toggle grade format action hook', () => {
|
||||||
|
render(<ScoreViewInput />);
|
||||||
|
|
||||||
|
expect(actions.grades.useToggleGradeFormat).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('responds to different grade format values', () => {
|
||||||
|
const { rerender } = render(<ScoreViewInput />);
|
||||||
|
|
||||||
|
let select = screen.getByRole('combobox', { name: /score view/i });
|
||||||
|
expect(select).toHaveValue('percent');
|
||||||
|
|
||||||
|
selectors.grades.useGradeData.mockReturnValue({
|
||||||
|
gradeFormat: 'absolute',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
rerender(<ScoreViewInput />);
|
||||||
|
select = screen.getByRole('combobox', { name: /score view/i });
|
||||||
|
expect(select).toHaveValue('absolute');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('user interactions', () => {
|
||||||
|
it('handles option selection', async () => {
|
||||||
|
render(<ScoreViewInput />);
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
const select = screen.getByRole('combobox', { name: /score view/i });
|
||||||
|
|
||||||
|
await user.selectOptions(select, 'absolute');
|
||||||
|
|
||||||
|
expect(mockToggleFormat).toHaveBeenCalledWith(expect.any(Object));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maintains state consistency', () => {
|
||||||
|
render(<ScoreViewInput />);
|
||||||
|
|
||||||
|
const select = screen.getByRole('combobox', { name: /score view/i });
|
||||||
|
const percentOption = screen.getByRole('option', { name: /percent/i });
|
||||||
|
|
||||||
|
expect(select).toHaveValue('percent');
|
||||||
|
expect(percentOption).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('internationalization', () => {
|
||||||
|
it('displays localized label text', () => {
|
||||||
|
render(<ScoreViewInput />);
|
||||||
|
|
||||||
|
expect(screen.getByText('Score View:')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays localized option text', () => {
|
||||||
|
render(<ScoreViewInput />);
|
||||||
|
|
||||||
|
expect(screen.getByText('Percent')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Absolute')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('component structure', () => {
|
||||||
|
it('uses proper Bootstrap form classes', () => {
|
||||||
|
render(<ScoreViewInput />);
|
||||||
|
|
||||||
|
const select = screen.getByRole('combobox', { name: /score view/i });
|
||||||
|
expect(select).toHaveClass('form-control');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders within form group structure', () => {
|
||||||
|
render(<ScoreViewInput />);
|
||||||
|
|
||||||
|
const label = screen.getByText(/score view/i);
|
||||||
|
const select = screen.getByRole('combobox', { name: /score view/i });
|
||||||
|
|
||||||
|
expect(label).toBeInTheDocument();
|
||||||
|
expect(select).toBeInTheDocument();
|
||||||
|
expect(select).toHaveAccessibleName(expect.stringMatching(/score view/i));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`SearchControls component render snapshot 1`] = `
|
|
||||||
<div
|
|
||||||
className="search-container"
|
|
||||||
>
|
|
||||||
<SearchField
|
|
||||||
inputLabel="test-input-label"
|
|
||||||
onBlur={[MockFunction hooks.onBlur]}
|
|
||||||
onClear={[MockFunction hooks.onClear]}
|
|
||||||
onSubmit={[MockFunction hooks.onSubmit]}
|
|
||||||
value="test-search-value"
|
|
||||||
/>
|
|
||||||
<small
|
|
||||||
className="form-text text-muted search-help-text"
|
|
||||||
>
|
|
||||||
test-hint-text
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -18,6 +18,18 @@ jest.mock('data/redux/hooks', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('react', () => ({
|
||||||
|
...jest.requireActual('react'),
|
||||||
|
useContext: jest.fn(context => context),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform/i18n', () => ({
|
||||||
|
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||||
|
useIntl: jest.fn(() => ({
|
||||||
|
formatMessage: (message) => message.defaultMessage,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
const searchValue = 'test-search-value';
|
const searchValue = 'test-search-value';
|
||||||
selectors.app.useSearchValue.mockReturnValue(searchValue);
|
selectors.app.useSearchValue.mockReturnValue(searchValue);
|
||||||
const setSearchValue = jest.fn();
|
const setSearchValue = jest.fn();
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const SearchControls = () => {
|
|||||||
<div className="search-container">
|
<div className="search-container">
|
||||||
<SearchField
|
<SearchField
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
inputLabel={inputLabel}
|
label={inputLabel}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
onClear={onClear}
|
onClear={onClear}
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { initializeMocks, render, screen } from 'testUtilsExtra';
|
||||||
|
|
||||||
import { SearchField } from '@openedx/paragon';
|
|
||||||
|
|
||||||
import useSearchControlsData from './hooks';
|
import useSearchControlsData from './hooks';
|
||||||
import SearchControls from '.';
|
import SearchControls from '.';
|
||||||
@@ -17,32 +15,19 @@ const hookProps = {
|
|||||||
hintText: 'test-hint-text',
|
hintText: 'test-hint-text',
|
||||||
};
|
};
|
||||||
useSearchControlsData.mockReturnValue(hookProps);
|
useSearchControlsData.mockReturnValue(hookProps);
|
||||||
|
|
||||||
let el;
|
|
||||||
describe('SearchControls component', () => {
|
describe('SearchControls component', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
initializeMocks();
|
||||||
|
render(<SearchControls />);
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
el = shallow(<SearchControls />);
|
|
||||||
});
|
|
||||||
describe('behavior', () => {
|
|
||||||
it('initializes component hooks', () => {
|
|
||||||
expect(useSearchControlsData).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
test('snapshot', () => {
|
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
test('search field', () => {
|
test('search field', () => {
|
||||||
const { props } = el.instance.findByType(SearchField)[0];
|
expect(screen.getByLabelText(hookProps.inputLabel)).toBeInTheDocument();
|
||||||
expect(props.onSubmit).toEqual(hookProps.onSubmit);
|
expect(screen.getByRole('searchbox')).toHaveValue(hookProps.searchValue);
|
||||||
expect(props.onBlur).toEqual(hookProps.onBlur);
|
|
||||||
expect(props.onClear).toEqual(hookProps.onClear);
|
|
||||||
expect(props.inputLabel).toEqual(hookProps.inputLabel);
|
|
||||||
expect(props.value).toEqual(hookProps.searchValue);
|
|
||||||
});
|
});
|
||||||
test('hint text', () => {
|
test('hint text', () => {
|
||||||
expect(el.instance.findByType('small')[0].children[0].el).toEqual(hookProps.hintText);
|
expect(screen.getByText(hookProps.hintText)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { render } from '@testing-library/react';
|
||||||
|
|
||||||
import { selectors } from 'data/redux/hooks';
|
import { selectors } from 'data/redux/hooks';
|
||||||
import SpinnerIcon from './SpinnerIcon';
|
import SpinnerIcon from './SpinnerIcon';
|
||||||
@@ -10,26 +10,19 @@ jest.mock('data/redux/hooks', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
selectors.root.useShouldShowSpinner.mockReturnValue(true);
|
|
||||||
let el;
|
|
||||||
describe('SpinnerIcon', () => {
|
describe('SpinnerIcon', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
el = shallow(<SpinnerIcon />);
|
|
||||||
});
|
});
|
||||||
describe('behavior', () => {
|
it('does not render if show: false', () => {
|
||||||
it('initializes redux hook', () => {
|
selectors.root.useShouldShowSpinner.mockReturnValueOnce(false);
|
||||||
expect(selectors.root.useShouldShowSpinner).toHaveBeenCalled();
|
const { container } = render(<SpinnerIcon />);
|
||||||
});
|
expect(container.querySelector('.fa.fa-spinner')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
describe('component', () => {
|
|
||||||
it('does not render if show: false', () => {
|
test('displays spinner overlay with spinner icon', () => {
|
||||||
selectors.root.useShouldShowSpinner.mockReturnValueOnce(false);
|
selectors.root.useShouldShowSpinner.mockReturnValueOnce(true);
|
||||||
el = shallow(<SpinnerIcon />);
|
const { container } = render(<SpinnerIcon />);
|
||||||
expect(el.isEmptyRender()).toEqual(true);
|
expect(container.querySelector('.fa.fa-spinner')).toBeInTheDocument();
|
||||||
});
|
|
||||||
test('snapshot - displays spinner overlay with spinner icon', () => {
|
|
||||||
expect(el.snapshot).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`StatusAlerts component render snapshot 1`] = `
|
|
||||||
<Fragment>
|
|
||||||
<Alert
|
|
||||||
onClose={[MockFunction hooks.successBanner.onClose]}
|
|
||||||
show="hooks.show-success-banner"
|
|
||||||
variant="success"
|
|
||||||
>
|
|
||||||
hooks.success-banner-text
|
|
||||||
</Alert>
|
|
||||||
<Alert
|
|
||||||
dismissible={false}
|
|
||||||
show="hooks.show-grade-filter"
|
|
||||||
variant="danger"
|
|
||||||
>
|
|
||||||
hooks.grade-filter-text
|
|
||||||
</Alert>
|
|
||||||
</Fragment>
|
|
||||||
`;
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import { formatMessage } from 'testUtils';
|
|
||||||
import { actions, selectors } from 'data/redux/hooks';
|
import { actions, selectors } from 'data/redux/hooks';
|
||||||
|
|
||||||
import useStatusAlertsData from './hooks';
|
import useStatusAlertsData from './hooks';
|
||||||
@@ -16,6 +15,18 @@ jest.mock('data/redux/hooks', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('react', () => ({
|
||||||
|
...jest.requireActual('react'),
|
||||||
|
useContext: jest.fn(context => context),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform/i18n', () => ({
|
||||||
|
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||||
|
useIntl: jest.fn(() => ({
|
||||||
|
formatMessage: (message) => message.defaultMessage,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
const validity = {
|
const validity = {
|
||||||
isMinValid: true,
|
isMinValid: true,
|
||||||
isMaxValid: true,
|
isMaxValid: true,
|
||||||
@@ -49,7 +60,7 @@ describe('useStatusAlertsData', () => {
|
|||||||
expect(out.successBanner.show).toEqual(showSuccess);
|
expect(out.successBanner.show).toEqual(showSuccess);
|
||||||
});
|
});
|
||||||
test('message', () => {
|
test('message', () => {
|
||||||
expect(out.successBanner.text).toEqual(formatMessage(messages.editSuccessAlert));
|
expect(out.successBanner.text).toEqual(messages.editSuccessAlert.defaultMessage);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('gradeFilter', () => {
|
describe('gradeFilter', () => {
|
||||||
@@ -70,7 +81,7 @@ describe('useStatusAlertsData', () => {
|
|||||||
expect(out.gradeFilter.show).toEqual(true);
|
expect(out.gradeFilter.show).toEqual(true);
|
||||||
});
|
});
|
||||||
test('filter message', () => {
|
test('filter message', () => {
|
||||||
expect(out.gradeFilter.text).toEqual(formatMessage(messages.minGradeInvalid));
|
expect(out.gradeFilter.text).toEqual(messages.minGradeInvalid.defaultMessage);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('max filter is invalid', () => {
|
describe('max filter is invalid', () => {
|
||||||
@@ -85,7 +96,7 @@ describe('useStatusAlertsData', () => {
|
|||||||
expect(out.gradeFilter.show).toEqual(true);
|
expect(out.gradeFilter.show).toEqual(true);
|
||||||
});
|
});
|
||||||
test('filter message', () => {
|
test('filter message', () => {
|
||||||
expect(out.gradeFilter.text).toEqual(formatMessage(messages.maxGradeInvalid));
|
expect(out.gradeFilter.text).toEqual(messages.maxGradeInvalid.defaultMessage);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('both filters are invalid', () => {
|
describe('both filters are invalid', () => {
|
||||||
@@ -101,7 +112,7 @@ describe('useStatusAlertsData', () => {
|
|||||||
});
|
});
|
||||||
test('filter message', () => {
|
test('filter message', () => {
|
||||||
expect(out.gradeFilter.text).toEqual(
|
expect(out.gradeFilter.text).toEqual(
|
||||||
`${formatMessage(messages.minGradeInvalid)}${formatMessage(messages.maxGradeInvalid)}`,
|
`${messages.minGradeInvalid.defaultMessage}${messages.maxGradeInvalid.defaultMessage}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user