Compare commits
2 Commits
master
...
open-relea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cb95abe14 | ||
|
|
4b4ba11932 |
2
.env
2
.env
@@ -21,5 +21,3 @@ USER_INFO_COOKIE_NAME=''
|
||||
SCHEDULE_EMAIL_SECTION=''
|
||||
APP_ID=''
|
||||
MFE_CONFIG_API_URL=''
|
||||
# Fallback in local style files
|
||||
PARAGON_THEME_URLS={}
|
||||
|
||||
@@ -22,5 +22,3 @@ USER_INFO_COOKIE_NAME='edx-user-info'
|
||||
SCHEDULE_EMAIL_SECTION='true'
|
||||
APP_ID=''
|
||||
MFE_CONFIG_API_URL=''
|
||||
# Fallback in local style files
|
||||
PARAGON_THEME_URLS={}
|
||||
|
||||
@@ -20,4 +20,3 @@ USER_INFO_COOKIE_NAME='edx-user-info'
|
||||
SCHEDULE_EMAIL_SECTION='true'
|
||||
APP_ID=''
|
||||
MFE_CONFIG_API_URL=''
|
||||
PARAGON_THEME_URLS={}
|
||||
|
||||
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@@ -18,9 +18,9 @@ jobs:
|
||||
- name: Setup Nodejs Env
|
||||
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
|
||||
- name: Setup Nodejs
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
node-version: ${{ env.NODE_VER }}
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Validate package-lock.json changes
|
||||
@@ -34,7 +34,4 @@ jobs:
|
||||
- name: i18n_extract
|
||||
run: npm run i18n_extract
|
||||
- name: Coverage
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: true
|
||||
uses: codecov/codecov-action@v3
|
||||
|
||||
2
.github/workflows/lockfileversion-check.yml
vendored
2
.github/workflows/lockfileversion-check.yml
vendored
@@ -10,4 +10,4 @@ on:
|
||||
|
||||
jobs:
|
||||
version-check:
|
||||
uses: openedx/.github/.github/workflows/lockfileversion-check-v3.yml@master
|
||||
uses: openedx/.github/.github/workflows/lockfile-check.yml@master
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,7 +5,6 @@ node_modules
|
||||
npm-debug.log
|
||||
coverage
|
||||
module.config.js
|
||||
env.config.*
|
||||
|
||||
dist/
|
||||
src/i18n/transifex_input.json
|
||||
|
||||
3
Makefile
3
Makefile
@@ -37,10 +37,11 @@ pull_translations:
|
||||
translations/frontend-component-header/src/i18n/messages:frontend-component-header \
|
||||
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
|
||||
translations/paragon/src/i18n/messages:paragon \
|
||||
translations/frontend-lib-content-components/src/i18n/messages:frontend-lib-content-components \
|
||||
translations/frontend-platform/src/i18n/messages:frontend-platform \
|
||||
translations/frontend-app-communications/src/i18n/messages:frontend-app-communications
|
||||
|
||||
$(intl_imports) frontend-component-header frontend-component-footer paragon frontend-platform frontend-app-communications
|
||||
$(intl_imports) frontend-component-header frontend-component-footer paragon frontend-lib-content-components frontend-platform frontend-app-communications
|
||||
|
||||
# This target is used by Travis.
|
||||
validate-no-uncommitted-package-lock-changes:
|
||||
|
||||
70
README.rst
70
README.rst
@@ -1,72 +1,58 @@
|
||||
frontend-app-communications
|
||||
###########################
|
||||
#############################
|
||||
|
||||
|license-badge| |status-badge| |ci-badge| |codecov-badge|
|
||||
|
||||
|
||||
Purpose
|
||||
********
|
||||
*******
|
||||
|
||||
A tool used by course teams to communicate with their learners. The interface for anything related to instructor-to-learner communications. Instructor bulk email, for example.
|
||||
A tool used by course teams to communicate with their learners. The interface for anything related to instructor to learner communications. Instructor bulk email, for example.
|
||||
|
||||
Getting Started
|
||||
***************
|
||||
|
||||
Prerequisites
|
||||
=============
|
||||
|
||||
`Tutor`_ is currently recommended as a development environment for your
|
||||
new MFE. Please refer
|
||||
to the `relevant tutor-mfe documentation`_ to get started using it.
|
||||
|
||||
.. _Tutor: https://github.com/overhangio/tutor
|
||||
|
||||
.. _relevant tutor-mfe documentation: https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development
|
||||
Getting started
|
||||
------------
|
||||
|
||||
For now, this repo is not intergrated with devstack. You'll be running the app locally and not through docker. This does make setup a little easier.
|
||||
|
||||
Cloning and Startup
|
||||
===================
|
||||
|
||||
1. Clone your new repo:
|
||||
1. Clone your new repo:
|
||||
|
||||
.. code-block:: bash
|
||||
``git clone https://github.com/edx/frontend-app-communications.git``
|
||||
|
||||
git clone https://github.com/edx/frontend-app-communications.git
|
||||
2. Use node v18.x.
|
||||
|
||||
2. Use the version of Node specified in the ``.nvmrc`` file.
|
||||
The current version of the micro-frontend build scripts support node 18.
|
||||
Using other major versions of node *may* work, but this is unsupported. For
|
||||
convenience, this repository includes an .nvmrc file to help in setting the
|
||||
correct node version via `nvm <https://github.com/nvm-sh/nvm>`_.
|
||||
|
||||
The current version of the micro-frontend build scripts supports the version of Node found in ``.nvmrc``.
|
||||
Using other major versions of node *may* work, but this is unsupported. For
|
||||
convenience, this repository includes a ``.nvmrc`` file to help in setting the
|
||||
correct node version via `nvm <https://github.com/nvm-sh/nvm>`_.
|
||||
3. Install npm dependencies:
|
||||
|
||||
3. Install npm dependencies:
|
||||
``cd frontend-app-communications && npm install``
|
||||
|
||||
.. code-block:: bash
|
||||
4. Update the application port to use for local development:
|
||||
|
||||
cd frontend-app-communications && npm install
|
||||
Default port is 1984. If this does not work for you, update the line
|
||||
`PORT=1984` to your port in all .env.* files
|
||||
|
||||
4. Update the application port to use for local development:
|
||||
5. Start the devserver. The app will be running at ``localhost:1984``, or whatever port you change it too.
|
||||
|
||||
The default port is 1984. If this does not work for you, update the line
|
||||
``PORT=1984`` to your port in all ``.env.*`` files
|
||||
.. code-block::
|
||||
|
||||
5. Start the devserver. The app will be running at ``localhost:1984``, or whatever port you change it too.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
npm start
|
||||
npm start
|
||||
|
||||
|
||||
Environment Variables/Setup Notes
|
||||
---------------------------------
|
||||
|
||||
If you wish to add new environment variables for local testing, they should be listed in 2 places:
|
||||
If you wish to add new environment varibles for local testing, they should be listed in 2 places:
|
||||
|
||||
1. In ``.env.development``
|
||||
2. Added to the ``mergeConfig`` found in ``src/index.jsx``
|
||||
|
||||
.. code-block:: jsx
|
||||
.. code-block::
|
||||
|
||||
initialize({
|
||||
config: () => {
|
||||
@@ -75,13 +61,13 @@ If you wish to add new environment variables for local testing, they should be l
|
||||
}, 'CommuncationsAppConfig');
|
||||
|
||||
Running Tests
|
||||
-------------
|
||||
---------------------------
|
||||
|
||||
Tests use `jest` and `react-test-library`. To run all the tests for this repo:
|
||||
|
||||
.. code-block::
|
||||
.. code-block::
|
||||
|
||||
npm test
|
||||
npm test
|
||||
|
||||
Plugins
|
||||
=======
|
||||
@@ -96,7 +82,7 @@ The production build is created with ``npm run build``.
|
||||
Internationalization
|
||||
====================
|
||||
|
||||
Please refer to the `frontend-platform i18n howto`_ for documentation on
|
||||
Please see refer to the `frontend-platform i18n howto`_ for documentation on
|
||||
internationalization.
|
||||
|
||||
.. _frontend-platform i18n howto: https://github.com/openedx/frontend-platform/blob/master/docs/how_tos/i18n.rst
|
||||
@@ -179,4 +165,4 @@ Please do not report security issues in public, and email security@openedx.org i
|
||||
|
||||
.. |codecov-badge| image:: https://codecov.io/github/openedx/frontend-app-communications/coverage.svg?branch=master
|
||||
:target: https://codecov.io/github/openedx/frontend-app-communications?branch=master
|
||||
:alt: Codecov
|
||||
:alt: Codecov
|
||||
@@ -12,7 +12,6 @@ metadata:
|
||||
icon: "Article"
|
||||
annotations:
|
||||
openedx.org/arch-interest-groups: ""
|
||||
openedx.org/release: "master"
|
||||
spec:
|
||||
owner: group:committers-frontend
|
||||
type: "service"
|
||||
|
||||
9
openedx.yaml
Normal file
9
openedx.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
# This file describes this Open edX repo, as described in OEP-2:
|
||||
# http://open-edx-proposals.readthedocs.io/en/latest/oeps/oep-0002.html#specification
|
||||
|
||||
oeps: {}
|
||||
openedx-release:
|
||||
# The openedx-release key is described in OEP-10:
|
||||
# https://open-edx-proposals.readthedocs.io/en/latest/oep-0010-proc-openedx-releases.html
|
||||
# The FAQ might also be helpful: https://openedx.atlassian.net/wiki/spaces/COMM/pages/1331268879/Open+edX+Release+FAQ
|
||||
ref: master
|
||||
24358
package-lock.json
generated
24358
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
35
package.json
35
package.json
@@ -12,13 +12,17 @@
|
||||
"scripts": {
|
||||
"build": "fedx-scripts webpack",
|
||||
"i18n_extract": "fedx-scripts formatjs extract",
|
||||
"lint": "fedx-scripts eslint --ext .js --ext .jsx src/",
|
||||
"lint:fix": "fedx-scripts eslint --fix --ext .js --ext .jsx src/",
|
||||
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
|
||||
"lint:fix": "fedx-scripts eslint --fix --ext .js --ext .jsx .",
|
||||
"snapshot": "fedx-scripts jest --updateSnapshot",
|
||||
"start": "fedx-scripts webpack-dev-server --progress",
|
||||
"dev": "PUBLIC_PATH=/communications/ MFE_CONFIG_API_URL='http://localhost:8000/api/mfe_config/v1' fedx-scripts webpack-dev-server --progress --host apps.local.openedx.io",
|
||||
"test": "TZ=UTC fedx-scripts jest --coverage --passWithNoTests"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "npm run lint"
|
||||
}
|
||||
},
|
||||
"author": "edX",
|
||||
"license": "AGPL-3.0",
|
||||
"homepage": "https://github.com/edx/frontend-app-communications#readme",
|
||||
@@ -30,9 +34,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
|
||||
"@edx/frontend-component-footer": "^14.6.0",
|
||||
"@edx/frontend-component-header": "^6.6.1",
|
||||
"@edx/frontend-platform": "^8.3.7",
|
||||
"@edx/frontend-component-header": "5.0.2",
|
||||
"@edx/frontend-platform": "7.0.1",
|
||||
"@edx/openedx-atlas": "^0.6.0",
|
||||
"@edx/tinymce-language-selector": "1.1.0",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
||||
@@ -40,8 +43,9 @@
|
||||
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||
"@fortawesome/react-fontawesome": "0.2.0",
|
||||
"@openedx/frontend-plugin-framework": "^1.6.0",
|
||||
"@openedx/paragon": "^23.3.0",
|
||||
"@openedx/frontend-plugin-framework": "^1.1.2",
|
||||
"@openedx/frontend-slot-footer": "^1.0.2",
|
||||
"@openedx/paragon": "^22.0.0",
|
||||
"@tinymce/tinymce-react": "3.14.0",
|
||||
"axios": "0.27.2",
|
||||
"classnames": "2.3.2",
|
||||
@@ -49,8 +53,8 @@
|
||||
"jquery": "3.6.1",
|
||||
"popper.js": "1.16.1",
|
||||
"prop-types": "15.8.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-redux": "7.2.9",
|
||||
"react-router": "6.15.0",
|
||||
@@ -61,13 +65,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@edx/browserslist-config": "^1.2.0",
|
||||
"@edx/typescript-config": "^1.1.0",
|
||||
"@openedx/frontend-build": "^14.6.2",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@edx/reactifex": "^2.1.1",
|
||||
"@openedx/frontend-build": "13.0.27",
|
||||
"@testing-library/jest-dom": "5.16.5",
|
||||
"@testing-library/react": "12.1.5",
|
||||
"axios-mock-adapter": "1.21.2",
|
||||
"glob": "7.2.3",
|
||||
"jest": "29.7.0",
|
||||
"husky": "7.0.4",
|
||||
"jest": "27.5.1",
|
||||
"prettier": "2.8.1",
|
||||
"rosie": "2.1.0"
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`app registry subscribe: APP_INIT_ERROR. snapshot: displays an ErrorPage to root element 1`] = `
|
||||
<UNDEFINED>
|
||||
<ErrorPage
|
||||
message="test-error-message"
|
||||
/>
|
||||
</UNDEFINED>
|
||||
`;
|
||||
|
||||
exports[`app registry subscribe: APP_READY. links App to root element 1`] = `
|
||||
<UNDEFINED>
|
||||
<AppProvider>
|
||||
<HelmetWrapper
|
||||
defer={true}
|
||||
encodeSpecialCharacters={true}
|
||||
>
|
||||
<link
|
||||
href="favicon-url"
|
||||
rel="shortcut icon"
|
||||
type="image/x-icon"
|
||||
/>
|
||||
</HelmetWrapper>
|
||||
<Routes>
|
||||
<Route
|
||||
element={
|
||||
<AuthenticatedPageRoute>
|
||||
<Page Container>
|
||||
<Bulk Email Tool />
|
||||
</Page Container>
|
||||
</AuthenticatedPageRoute>
|
||||
}
|
||||
path="/courses/:courseId/bulk_email"
|
||||
/>
|
||||
</Routes>
|
||||
</AppProvider>
|
||||
</UNDEFINED>
|
||||
`;
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
import {
|
||||
SpinnerSimple, Cancel, Send, Event, Check,
|
||||
} from '@openedx/paragon/icons';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import classNames from 'classnames';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import TextEditor from '../text-editor/TextEditor';
|
||||
@@ -51,8 +51,8 @@ function BulkEmailForm(props) {
|
||||
courseId,
|
||||
cohorts,
|
||||
courseModes,
|
||||
intl,
|
||||
} = props;
|
||||
const intl = useIntl();
|
||||
const [{ editor }, dispatch] = useContext(BulkEmailContext);
|
||||
const [emailFormStatus, setEmailFormStatus] = useState(FORM_SUBMIT_STATES.DEFAULT);
|
||||
const [emailFormValidation, setEmailFormValidation] = useState({
|
||||
@@ -392,7 +392,7 @@ BulkEmailForm.defaultProps = {
|
||||
BulkEmailForm.propTypes = {
|
||||
courseId: PropTypes.string.isRequired,
|
||||
cohorts: PropTypes.arrayOf(PropTypes.string),
|
||||
|
||||
intl: intlShape.isRequired,
|
||||
courseModes: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
slug: PropTypes.string.isRequired,
|
||||
@@ -401,4 +401,4 @@ BulkEmailForm.propTypes = {
|
||||
).isRequired,
|
||||
};
|
||||
|
||||
export default BulkEmailForm;
|
||||
export default injectIntl(BulkEmailForm);
|
||||
|
||||
@@ -39,7 +39,7 @@ function renderBulkEmailForm() {
|
||||
function renderBulkEmailFormContext(value) {
|
||||
return (
|
||||
<BulkEmailContext.Provider value={[value, dispatchMock]}>
|
||||
<BulkEmailForm courseId="test" courseModes={courseMode} />
|
||||
<BulkEmailForm courseId="test" courseMode={courseMode} />
|
||||
</BulkEmailContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import {
|
||||
Button, Collapsible, Icon,
|
||||
@@ -14,8 +14,7 @@ import { getSentEmailHistory } from './data/api';
|
||||
import BulkEmailTaskManagerTable from './BulkEmailHistoryTable';
|
||||
import ViewEmailModal from './ViewEmailModal';
|
||||
|
||||
function BulkEmailContentHistory() {
|
||||
const intl = useIntl();
|
||||
function BulkEmailContentHistory({ intl }) {
|
||||
const { courseId } = useParams();
|
||||
const [emailHistoryData, setEmailHistoryData] = useState();
|
||||
const [errorRetrievingData, setErrorRetrievingData] = useState(false);
|
||||
@@ -155,6 +154,7 @@ function BulkEmailContentHistory() {
|
||||
}
|
||||
|
||||
BulkEmailContentHistory.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
row: PropTypes.shape({
|
||||
index: PropTypes.number,
|
||||
}),
|
||||
@@ -164,4 +164,4 @@ BulkEmailContentHistory.defaultProps = {
|
||||
row: {},
|
||||
};
|
||||
|
||||
export default BulkEmailContentHistory;
|
||||
export default injectIntl(BulkEmailContentHistory);
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { getInstructorTasks } from './data/api';
|
||||
import messages from './messages';
|
||||
import useInterval from '../../../utils/useInterval';
|
||||
import BulkEmailTaskManagerTable from './BulkEmailHistoryTable';
|
||||
|
||||
function BulkEmailPendingTasks() {
|
||||
const intl = useIntl();
|
||||
function BulkEmailPendingTasks({ intl }) {
|
||||
const { courseId } = useParams();
|
||||
|
||||
const [instructorTaskData, setInstructorTaskData] = useState();
|
||||
@@ -90,4 +89,8 @@ function BulkEmailPendingTasks() {
|
||||
);
|
||||
}
|
||||
|
||||
export default BulkEmailPendingTasks;
|
||||
BulkEmailPendingTasks.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(BulkEmailPendingTasks);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { Icon, Collapsible } from '@openedx/paragon';
|
||||
import { SpinnerSimple } from '@openedx/paragon/icons';
|
||||
@@ -11,8 +11,7 @@ import BulkEmailTaskManagerTable from './BulkEmailHistoryTable';
|
||||
|
||||
import './bulkEmailTaskHistory.scss';
|
||||
|
||||
function BulkEmailTaskHistory() {
|
||||
const intl = useIntl();
|
||||
function BulkEmailTaskHistory({ intl }) {
|
||||
const { courseId } = useParams();
|
||||
|
||||
const [emailTaskHistoryData, setEmailTaskHistoryData] = useState([]);
|
||||
@@ -118,4 +117,8 @@ function BulkEmailTaskHistory() {
|
||||
);
|
||||
}
|
||||
|
||||
export default BulkEmailTaskHistory;
|
||||
BulkEmailTaskHistory.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(BulkEmailTaskHistory);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import BulkEmailContentHistory from './BulkEmailContentHistory';
|
||||
import BulkEmailTaskHistory from './BulkEmailTaskHistory';
|
||||
@@ -9,8 +9,7 @@ import messages from './messages';
|
||||
import BulkEmailScheduledEmailsTable from './bulk-email-scheduled-emails-table';
|
||||
import BulkEmailPendingTasksAlert from './BulkEmailPendingTasksAlert';
|
||||
|
||||
function BulkEmailTaskManager({ courseId }) {
|
||||
const intl = useIntl();
|
||||
function BulkEmailTaskManager({ intl, courseId }) {
|
||||
return (
|
||||
<div className="w-100">
|
||||
{getConfig().SCHEDULE_EMAIL_SECTION && (
|
||||
@@ -35,7 +34,8 @@ function BulkEmailTaskManager({ courseId }) {
|
||||
}
|
||||
|
||||
BulkEmailTaskManager.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
courseId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default BulkEmailTaskManager;
|
||||
export default injectIntl(BulkEmailTaskManager);
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ActionRow, Button, ModalDialog } from '@openedx/paragon';
|
||||
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import messages from './messages';
|
||||
import { BulkEmailContext } from '../bulk-email-context';
|
||||
import { copyToEditor } from '../bulk-email-form/data/actions';
|
||||
|
||||
function ViewEmailModal({
|
||||
messageContent, isOpen, setModalOpen,
|
||||
intl, messageContent, isOpen, setModalOpen,
|
||||
}) {
|
||||
const intl = useIntl();
|
||||
const [, dispatch] = useContext(BulkEmailContext);
|
||||
return (
|
||||
<div>
|
||||
@@ -73,6 +72,7 @@ function ViewEmailModal({
|
||||
}
|
||||
|
||||
ViewEmailModal.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
messageContent: PropTypes.shape({
|
||||
subject: PropTypes.string,
|
||||
requester: PropTypes.string,
|
||||
@@ -86,4 +86,4 @@ ViewEmailModal.propTypes = {
|
||||
setModalOpen: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ViewEmailModal;
|
||||
export default injectIntl(ViewEmailModal);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import React, {
|
||||
useCallback, useContext, useState, useEffect,
|
||||
} from 'react';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Alert, DataTable, Icon, IconButton, useToggle,
|
||||
} from '@openedx/paragon';
|
||||
@@ -32,8 +32,7 @@ function flattenScheduledEmailsArray(emails) {
|
||||
}));
|
||||
}
|
||||
|
||||
function BulkEmailScheduledEmailsTable() {
|
||||
const intl = useIntl();
|
||||
function BulkEmailScheduledEmailsTable({ intl }) {
|
||||
const { courseId } = useParams();
|
||||
const [{ scheduledEmailsTable }, dispatch] = useContext(BulkEmailContext);
|
||||
const [tableData, setTableData] = useState([]);
|
||||
@@ -197,4 +196,8 @@ function BulkEmailScheduledEmailsTable() {
|
||||
);
|
||||
}
|
||||
|
||||
export default BulkEmailScheduledEmailsTable;
|
||||
BulkEmailScheduledEmailsTable.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(BulkEmailScheduledEmailsTable);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import {
|
||||
render, screen, fireEvent, cleanup, initializeMockApp,
|
||||
render, screen, fireEvent, cleanup, act, initializeMockApp,
|
||||
} from '../../../../setupTest';
|
||||
import { BulkEmailProvider } from '../../bulk-email-context';
|
||||
import BulkEmailContentHistory from '../BulkEmailContentHistory';
|
||||
@@ -41,99 +41,107 @@ describe('BulkEmailContentHistory component', () => {
|
||||
});
|
||||
|
||||
test('renders a table when the button is pressed and data is returned', async () => {
|
||||
const emailHistoryData = buildEmailContentHistoryData(1);
|
||||
getSentEmailHistory.mockImplementation(() => emailHistoryData);
|
||||
await act(async () => {
|
||||
const emailHistoryData = buildEmailContentHistoryData(1);
|
||||
getSentEmailHistory.mockImplementation(() => emailHistoryData);
|
||||
|
||||
render(renderBulkEmailContentHistory());
|
||||
render(renderBulkEmailContentHistory());
|
||||
|
||||
const showEmailContentHistoryButton = await screen.findByText('Show Sent Email History');
|
||||
fireEvent.click(showEmailContentHistoryButton);
|
||||
const showEmailContentHistoryButton = await screen.findByText('Show Sent Email History');
|
||||
fireEvent.click(showEmailContentHistoryButton);
|
||||
|
||||
// verify component structure
|
||||
const tableDescription = await screen.findByText(
|
||||
'To read a sent email message, click the `View Message` button within the table.',
|
||||
);
|
||||
expect(tableDescription).toBeTruthy();
|
||||
// verify component structure
|
||||
const tableDescription = await screen.findByText(
|
||||
'To read a sent email message, click the `View Message` button within the table.',
|
||||
);
|
||||
expect(tableDescription).toBeTruthy();
|
||||
|
||||
// verify table structure
|
||||
expect(await screen.findByText('Subject')).toBeTruthy();
|
||||
expect(await screen.findByText('Sent By')).toBeTruthy();
|
||||
expect(await screen.findByText('Sent To')).toBeTruthy();
|
||||
expect(await screen.findByText('Time Sent')).toBeTruthy();
|
||||
expect(await screen.findByText('Number Sent')).toBeTruthy();
|
||||
// verify table structure
|
||||
expect(await screen.findByText('Subject')).toBeTruthy();
|
||||
expect(await screen.findByText('Sent By')).toBeTruthy();
|
||||
expect(await screen.findByText('Sent To')).toBeTruthy();
|
||||
expect(await screen.findByText('Time Sent')).toBeTruthy();
|
||||
expect(await screen.findByText('Number Sent')).toBeTruthy();
|
||||
|
||||
// verify table contents
|
||||
const { emails } = emailHistoryData;
|
||||
const email = emails[0];
|
||||
const createdDate = new Date(email.created).toLocaleString();
|
||||
expect(await screen.findByText(createdDate)).toBeTruthy();
|
||||
expect(await screen.findByText(email.number_sent)).toBeTruthy();
|
||||
expect(await screen.findByText(email.requester)).toBeTruthy();
|
||||
expect(await screen.findByText(email.sent_to.join(', '))).toBeTruthy();
|
||||
expect(await screen.findByText(email.email.subject)).toBeTruthy();
|
||||
// verify screen reader only <span />
|
||||
expect(await screen.findByText('0')).toHaveClass('sr-only');
|
||||
expect(await screen.findAllByText('View Message')).toBeTruthy();
|
||||
// verify table contents
|
||||
const { emails } = emailHistoryData;
|
||||
const email = emails[0];
|
||||
const createdDate = new Date(email.created).toLocaleString();
|
||||
expect(await screen.findByText(createdDate)).toBeTruthy();
|
||||
expect(await screen.findByText(email.number_sent)).toBeTruthy();
|
||||
expect(await screen.findByText(email.requester)).toBeTruthy();
|
||||
expect(await screen.findByText(email.sent_to.join(', '))).toBeTruthy();
|
||||
expect(await screen.findByText(email.email.subject)).toBeTruthy();
|
||||
// verify screen reader only <span />
|
||||
expect(await screen.findByText('0')).toHaveClass('sr-only');
|
||||
expect(await screen.findAllByText('View Message')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test('renders a modal that will display the contents of the previously sent message to a user', async () => {
|
||||
const emailHistoryData = buildEmailContentHistoryData(1);
|
||||
getSentEmailHistory.mockImplementation(() => emailHistoryData);
|
||||
await act(async () => {
|
||||
const emailHistoryData = buildEmailContentHistoryData(1);
|
||||
getSentEmailHistory.mockImplementation(() => emailHistoryData);
|
||||
|
||||
render(renderBulkEmailContentHistory());
|
||||
render(renderBulkEmailContentHistory());
|
||||
|
||||
const showEmailContentHistoryButton = await screen.findByText('Show Sent Email History');
|
||||
fireEvent.click(showEmailContentHistoryButton);
|
||||
const showEmailContentHistoryButton = await screen.findByText('Show Sent Email History');
|
||||
fireEvent.click(showEmailContentHistoryButton);
|
||||
|
||||
const viewMessageButton = await screen.findByText('View Message');
|
||||
fireEvent.click(viewMessageButton);
|
||||
const viewMessageButton = await screen.findByText('View Message');
|
||||
fireEvent.click(viewMessageButton);
|
||||
|
||||
// verify modal components and behavior
|
||||
const { emails } = emailHistoryData;
|
||||
const email = emails[0];
|
||||
const closeButton = await screen.findAllByText('Close');
|
||||
// verify modal components and behavior
|
||||
const { emails } = emailHistoryData;
|
||||
const email = emails[0];
|
||||
const closeButton = await screen.findAllByText('Close');
|
||||
|
||||
expect(closeButton).toBeTruthy();
|
||||
expect(await screen.findByText('Subject:')).toBeTruthy();
|
||||
expect(await screen.findByText('Sent by:')).toBeTruthy();
|
||||
expect(await screen.findByText('Time sent:')).toBeTruthy();
|
||||
expect(await screen.findByText('Sent to:')).toBeTruthy();
|
||||
expect(await screen.findByText('Message:')).toBeTruthy();
|
||||
expect(await screen.findAllByText(email.email.subject)).toBeTruthy();
|
||||
expect(await screen.findAllByText(email.requester)).toBeTruthy();
|
||||
const createdDate = new Date(email.created).toLocaleString();
|
||||
expect(await screen.findAllByText(createdDate)).toBeTruthy();
|
||||
expect(await screen.findAllByText(email.sent_to.join(', '))).toBeTruthy();
|
||||
// .replace() call strips the HTML tags from the string
|
||||
expect(await screen.findByText(email.email.html_message.replace(/<[^>]*>?/gm, ''))).toBeTruthy();
|
||||
expect(closeButton).toBeTruthy();
|
||||
expect(await screen.findByText('Subject:')).toBeTruthy();
|
||||
expect(await screen.findByText('Sent by:')).toBeTruthy();
|
||||
expect(await screen.findByText('Time sent:')).toBeTruthy();
|
||||
expect(await screen.findByText('Sent to:')).toBeTruthy();
|
||||
expect(await screen.findByText('Message:')).toBeTruthy();
|
||||
expect(await screen.findAllByText(email.email.subject)).toBeTruthy();
|
||||
expect(await screen.findAllByText(email.requester)).toBeTruthy();
|
||||
const createdDate = new Date(email.created).toLocaleString();
|
||||
expect(await screen.findAllByText(createdDate)).toBeTruthy();
|
||||
expect(await screen.findAllByText(email.sent_to.join(', '))).toBeTruthy();
|
||||
// .replace() call strips the HTML tags from the string
|
||||
expect(await screen.findByText(email.email.html_message.replace(/<[^>]*>?/gm, ''))).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test('renders a warning Alert when the button is pressed but there is no data to display', async () => {
|
||||
const emailHistoryData = buildEmailContentHistoryData(0);
|
||||
getSentEmailHistory.mockImplementation(() => emailHistoryData);
|
||||
// render the component
|
||||
render(renderBulkEmailContentHistory());
|
||||
// press the `show sent email history` button to initiate data retrieval
|
||||
const showEmailContentHistoryButton = await screen.findByText('Show Sent Email History');
|
||||
fireEvent.click(showEmailContentHistoryButton);
|
||||
// verify that an alert is displayed since the array of tasks is empty
|
||||
const alertMessage = await screen.findByText('There is no email history for this course.');
|
||||
expect(alertMessage).toBeTruthy();
|
||||
await act(async () => {
|
||||
const emailHistoryData = buildEmailContentHistoryData(0);
|
||||
getSentEmailHistory.mockImplementation(() => emailHistoryData);
|
||||
// render the component
|
||||
render(renderBulkEmailContentHistory());
|
||||
// press the `show sent email history` button to initiate data retrieval
|
||||
const showEmailContentHistoryButton = await screen.findByText('Show Sent Email History');
|
||||
fireEvent.click(showEmailContentHistoryButton);
|
||||
// verify that an alert is displayed since the array of tasks is empty
|
||||
const alertMessage = await screen.findByText('There is no email history for this course.');
|
||||
expect(alertMessage).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test('renders an error Alert when the button is pressed and an error occurs retrieving data', async () => {
|
||||
getSentEmailHistory.mockImplementation(() => {
|
||||
throw new Error();
|
||||
await act(async () => {
|
||||
getSentEmailHistory.mockImplementation(() => {
|
||||
throw new Error();
|
||||
});
|
||||
// render the component
|
||||
render(renderBulkEmailContentHistory());
|
||||
// press the `show sent email history` button to initiate data retrieval
|
||||
const showEmailContentHistoryButton = await screen.findByText('Show Sent Email History');
|
||||
fireEvent.click(showEmailContentHistoryButton);
|
||||
// verify that an alert is displayed since the array of tasks is empty
|
||||
const alertMessage = await screen.findByText(
|
||||
'An error occurred retrieving email history data for this course. Please try again later.',
|
||||
);
|
||||
expect(alertMessage).toBeTruthy();
|
||||
});
|
||||
// render the component
|
||||
render(renderBulkEmailContentHistory());
|
||||
// press the `show sent email history` button to initiate data retrieval
|
||||
const showEmailContentHistoryButton = await screen.findByText('Show Sent Email History');
|
||||
fireEvent.click(showEmailContentHistoryButton);
|
||||
// verify that an alert is displayed since the array of tasks is empty
|
||||
const alertMessage = await screen.findByText(
|
||||
'An error occurred retrieving email history data for this course. Please try again later.',
|
||||
);
|
||||
expect(alertMessage).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import {
|
||||
render, screen, fireEvent, cleanup, initializeMockApp,
|
||||
render, screen, fireEvent, cleanup, act, initializeMockApp,
|
||||
} from '../../../../setupTest';
|
||||
import BulkEmailTaskHistory from '../BulkEmailTaskHistory';
|
||||
import { getEmailTaskHistory } from '../data/api';
|
||||
@@ -32,66 +32,72 @@ describe('BulkEmailTaskHistory component', () => {
|
||||
});
|
||||
|
||||
test('renders a table properly when the button is pressed and data is returned', async () => {
|
||||
// build our mocked response
|
||||
const taskHistoryData = buildEmailTaskHistoryData(1);
|
||||
getEmailTaskHistory.mockImplementation(() => taskHistoryData);
|
||||
// render the component
|
||||
render(<BulkEmailTaskHistory />);
|
||||
// press the `show task history button` to initiate data retrieval and rendering of the table in our component
|
||||
const showTaskHistoryButton = await screen.findByText('Show Email Task History');
|
||||
fireEvent.click(showTaskHistoryButton);
|
||||
// verification of table structure
|
||||
expect(await screen.findByText('Task Type')).toBeTruthy();
|
||||
expect(await screen.findByText('Task Inputs')).toBeTruthy();
|
||||
expect(await screen.findByText('Task Id')).toBeTruthy();
|
||||
expect(await screen.findByText('Requester')).toBeTruthy();
|
||||
expect(await screen.findByText('Submitted')).toBeTruthy();
|
||||
expect(await screen.findByText('Duration (seconds)')).toBeTruthy();
|
||||
expect(await screen.findByText('State')).toBeTruthy();
|
||||
expect(await screen.findByText('Status')).toBeTruthy();
|
||||
expect(await screen.findByText('Task Progress')).toBeTruthy();
|
||||
expect(await screen.findAllByText('Showing 1 - 1 of 1.')).toBeTruthy();
|
||||
// verification of row contents
|
||||
const { tasks } = taskHistoryData;
|
||||
const task = tasks[0];
|
||||
const createdDate = new Date(task.created).toLocaleString();
|
||||
expect(await screen.findByText(createdDate)).toBeTruthy();
|
||||
expect(await screen.findByText(task.duration_sec)).toBeTruthy();
|
||||
expect(await screen.findByText(task.requester)).toBeTruthy();
|
||||
expect(await screen.findByText(task.status)).toBeTruthy();
|
||||
expect(await screen.findByText(task.task_id)).toBeTruthy();
|
||||
expect(await screen.findByText(task.task_input)).toBeTruthy();
|
||||
expect(await screen.findByText(task.task_message)).toBeTruthy();
|
||||
expect(await screen.findByText(task.task_state)).toBeTruthy();
|
||||
expect(await screen.findByText(task.task_type)).toBeTruthy();
|
||||
await act(async () => {
|
||||
// build our mocked response
|
||||
const taskHistoryData = buildEmailTaskHistoryData(1);
|
||||
getEmailTaskHistory.mockImplementation(() => taskHistoryData);
|
||||
// render the component
|
||||
render(<BulkEmailTaskHistory />);
|
||||
// press the `show task history button` to initiate data retrieval and rendering of the table in our component
|
||||
const showTaskHistoryButton = await screen.findByText('Show Email Task History');
|
||||
fireEvent.click(showTaskHistoryButton);
|
||||
// verification of table structure
|
||||
expect(await screen.findByText('Task Type')).toBeTruthy();
|
||||
expect(await screen.findByText('Task Inputs')).toBeTruthy();
|
||||
expect(await screen.findByText('Task Id')).toBeTruthy();
|
||||
expect(await screen.findByText('Requester')).toBeTruthy();
|
||||
expect(await screen.findByText('Submitted')).toBeTruthy();
|
||||
expect(await screen.findByText('Duration (seconds)')).toBeTruthy();
|
||||
expect(await screen.findByText('State')).toBeTruthy();
|
||||
expect(await screen.findByText('Status')).toBeTruthy();
|
||||
expect(await screen.findByText('Task Progress')).toBeTruthy();
|
||||
expect(await screen.findAllByText('Showing 1 - 1 of 1.')).toBeTruthy();
|
||||
// verification of row contents
|
||||
const { tasks } = taskHistoryData;
|
||||
const task = tasks[0];
|
||||
const createdDate = new Date(task.created).toLocaleString();
|
||||
expect(await screen.findByText(createdDate)).toBeTruthy();
|
||||
expect(await screen.findByText(task.duration_sec)).toBeTruthy();
|
||||
expect(await screen.findByText(task.requester)).toBeTruthy();
|
||||
expect(await screen.findByText(task.status)).toBeTruthy();
|
||||
expect(await screen.findByText(task.task_id)).toBeTruthy();
|
||||
expect(await screen.findByText(task.task_input)).toBeTruthy();
|
||||
expect(await screen.findByText(task.task_message)).toBeTruthy();
|
||||
expect(await screen.findByText(task.task_state)).toBeTruthy();
|
||||
expect(await screen.findByText(task.task_type)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test('renders a warning Alert when the button is pressed but there is no data to display', async () => {
|
||||
const taskHistoryData = buildEmailTaskHistoryData(0);
|
||||
getEmailTaskHistory.mockImplementation(() => taskHistoryData);
|
||||
// render the component
|
||||
render(<BulkEmailTaskHistory />);
|
||||
// press the `show task history` button to initiate data retrieval
|
||||
const showTaskHistoryButton = await screen.findByText('Show Email Task History');
|
||||
fireEvent.click(showTaskHistoryButton);
|
||||
// verify that an alert is displayed since the array of tasks is empty
|
||||
const alertMessage = await screen.findByText('There is no email task history for this course.');
|
||||
expect(alertMessage).toBeTruthy();
|
||||
await act(async () => {
|
||||
const taskHistoryData = buildEmailTaskHistoryData(0);
|
||||
getEmailTaskHistory.mockImplementation(() => taskHistoryData);
|
||||
// render the component
|
||||
render(<BulkEmailTaskHistory />);
|
||||
// press the `show task history` button to initiate data retrieval
|
||||
const showTaskHistoryButton = await screen.findByText('Show Email Task History');
|
||||
fireEvent.click(showTaskHistoryButton);
|
||||
// verify that an alert is displayed since the array of tasks is empty
|
||||
const alertMessage = await screen.findByText('There is no email task history for this course.');
|
||||
expect(alertMessage).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test('renders an error Alert when the button is pressed and an error occurs retrieving data', async () => {
|
||||
getEmailTaskHistory.mockImplementation(() => {
|
||||
throw new Error();
|
||||
await act(async () => {
|
||||
getEmailTaskHistory.mockImplementation(() => {
|
||||
throw new Error();
|
||||
});
|
||||
// render the component
|
||||
render(<BulkEmailTaskHistory />);
|
||||
// press the `show task history` button to initiate data retrieval
|
||||
const showTaskHistoryButton = await screen.findByText('Show Email Task History');
|
||||
fireEvent.click(showTaskHistoryButton);
|
||||
// verify that an alert is displayed since the array of tasks is empty
|
||||
const alertMessage = await screen.findByText(
|
||||
'Error fetching email task history data for this course. Please try again later.',
|
||||
);
|
||||
expect(alertMessage).toBeTruthy();
|
||||
});
|
||||
// render the component
|
||||
render(<BulkEmailTaskHistory />);
|
||||
// press the `show task history` button to initiate data retrieval
|
||||
const showTaskHistoryButton = await screen.findByText('Show Email Task History');
|
||||
fireEvent.click(showTaskHistoryButton);
|
||||
// verify that an alert is displayed since the array of tasks is empty
|
||||
const alertMessage = await screen.findByText(
|
||||
'Error fetching email task history data for this course. Please try again later.',
|
||||
);
|
||||
expect(alertMessage).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ActionRow, AlertModal, Button } from '@openedx/paragon';
|
||||
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
function TaskAlertModal(props) {
|
||||
const {
|
||||
isOpen, close, alertMessage,
|
||||
isOpen, close, alertMessage, intl,
|
||||
} = props;
|
||||
const intl = useIntl();
|
||||
|
||||
const messages = {
|
||||
taskAlertTitle: {
|
||||
id: 'bulk.email.task.alert.title',
|
||||
@@ -57,6 +57,7 @@ TaskAlertModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
close: PropTypes.func.isRequired,
|
||||
alertMessage: PropTypes.node.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default TaskAlertModal;
|
||||
export default injectIntl(TaskAlertModal);
|
||||
|
||||
@@ -17,8 +17,8 @@ import 'tinymce/plugins/image';
|
||||
import 'tinymce/plugins/codesample';
|
||||
import '@edx/tinymce-language-selector';
|
||||
|
||||
import contentUiCss from 'tinymce/skins/ui/oxide/content.css?raw';
|
||||
import contentCss from 'tinymce/skins/content/default/content.css?raw';
|
||||
import contentUiCss from 'tinymce/skins/ui/oxide/content.css';
|
||||
import contentCss from 'tinymce/skins/content/default/content.css';
|
||||
|
||||
export default function TextEditor(props) {
|
||||
const {
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { LearningHeader as Header } from '@edx/frontend-component-header';
|
||||
import { FooterSlot } from '@edx/frontend-component-footer';
|
||||
import FooterSlot from '@openedx/frontend-slot-footer';
|
||||
import { Spinner } from '@openedx/paragon';
|
||||
|
||||
import { getCohorts, getCourseHomeCourseMetadata } from './data/api';
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import React from 'react';
|
||||
import { Factory } from 'rosie';
|
||||
import {
|
||||
cleanup, initializeMockApp, render, screen,
|
||||
act, cleanup, initializeMockApp, render, screen,
|
||||
} from '../../../setupTest';
|
||||
|
||||
import PageContainer from '../PageContainer';
|
||||
@@ -32,32 +32,36 @@ describe('PageContainer', () => {
|
||||
afterEach(cleanup);
|
||||
|
||||
test('PageContainer renders properly when given course metadata', async () => {
|
||||
const cohorts = { cohorts: [Factory.build('cohort'), Factory.build('cohort')] };
|
||||
const courseMetadata = Factory.build('courseMetadata');
|
||||
await act(async () => {
|
||||
const cohorts = { cohorts: [Factory.build('cohort'), Factory.build('cohort')] };
|
||||
const courseMetadata = Factory.build('courseMetadata');
|
||||
|
||||
getCohorts.mockImplementation(() => cohorts);
|
||||
getCourseHomeCourseMetadata.mockImplementation(() => courseMetadata);
|
||||
getCohorts.mockImplementation(() => cohorts);
|
||||
getCourseHomeCourseMetadata.mockImplementation(() => courseMetadata);
|
||||
|
||||
render(<PageContainer />);
|
||||
render(<PageContainer />);
|
||||
|
||||
// Look for the org, title, and number of the course, which should be displayed in the Header.
|
||||
expect(await screen.findByText(`${courseMetadata.org} ${courseMetadata.number}`)).toBeTruthy();
|
||||
expect(await screen.findByText(courseMetadata.title)).toBeTruthy();
|
||||
// Look for the org, title, and number of the course, which should be displayed in the Header.
|
||||
expect(await screen.findByText(`${courseMetadata.org} ${courseMetadata.number}`)).toBeTruthy();
|
||||
expect(await screen.findByText(courseMetadata.title)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test('PageContainer renders children nested within it.', async () => {
|
||||
const cohorts = { cohorts: [Factory.build('cohort'), Factory.build('cohort')] };
|
||||
const courseMetadata = Factory.build('courseMetadata');
|
||||
await act(async () => {
|
||||
const cohorts = { cohorts: [Factory.build('cohort'), Factory.build('cohort')] };
|
||||
const courseMetadata = Factory.build('courseMetadata');
|
||||
|
||||
getCohorts.mockImplementation(() => cohorts);
|
||||
getCourseHomeCourseMetadata.mockImplementation(() => courseMetadata);
|
||||
getCohorts.mockImplementation(() => cohorts);
|
||||
getCourseHomeCourseMetadata.mockImplementation(() => courseMetadata);
|
||||
|
||||
render(
|
||||
<PageContainer>
|
||||
<span>Test Text</span>
|
||||
</PageContainer>,
|
||||
);
|
||||
render(
|
||||
<PageContainer>
|
||||
<span>Test Text</span>
|
||||
</PageContainer>,
|
||||
);
|
||||
|
||||
expect(await screen.findByText('Test Text')).toBeTruthy();
|
||||
expect(await screen.findByText('Test Text')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,8 +5,7 @@ import {
|
||||
APP_INIT_ERROR, APP_READY, subscribe, initialize, mergeConfig, getConfig,
|
||||
} from '@edx/frontend-platform';
|
||||
import { AppProvider, AuthenticatedPageRoute, ErrorPage } from '@edx/frontend-platform/react';
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
@@ -17,39 +16,30 @@ import BulkEmailTool from './components/bulk-email-tool';
|
||||
import PageContainer from './components/page-container/PageContainer';
|
||||
|
||||
subscribe(APP_READY, () => {
|
||||
const root = createRoot(document.getElementById('root'));
|
||||
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<AppProvider>
|
||||
<Helmet>
|
||||
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
|
||||
</Helmet>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/courses/:courseId/bulk_email"
|
||||
element={(
|
||||
<AuthenticatedPageRoute>
|
||||
<PageContainer>
|
||||
<BulkEmailTool />
|
||||
</PageContainer>
|
||||
</AuthenticatedPageRoute>
|
||||
ReactDOM.render(
|
||||
<AppProvider>
|
||||
<Helmet>
|
||||
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
|
||||
</Helmet>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/courses/:courseId/bulk_email"
|
||||
element={(
|
||||
<AuthenticatedPageRoute>
|
||||
<PageContainer>
|
||||
<BulkEmailTool />
|
||||
</PageContainer>
|
||||
</AuthenticatedPageRoute>
|
||||
)}
|
||||
/>
|
||||
</Routes>
|
||||
</AppProvider>
|
||||
</StrictMode>,
|
||||
/>
|
||||
</Routes>
|
||||
</AppProvider>,
|
||||
document.getElementById('root'),
|
||||
);
|
||||
});
|
||||
|
||||
subscribe(APP_INIT_ERROR, (error) => {
|
||||
const root = createRoot(document.getElementById('root'));
|
||||
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<ErrorPage message={error.message} />
|
||||
</StrictMode>,
|
||||
);
|
||||
ReactDOM.render(<ErrorPage message={error.message} />, document.getElementById('root'));
|
||||
});
|
||||
|
||||
initialize({
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
@use "@openedx/paragon/styles/css/core/custom-media-breakpoints" as paragonCustomMediaBreakpoints;
|
||||
@import "~@edx/brand/paragon/fonts";
|
||||
@import "~@edx/brand/paragon/variables";
|
||||
@import "~@openedx/paragon/scss/core/core";
|
||||
@import "~@edx/brand/paragon/overrides";
|
||||
|
||||
@import "~@edx/frontend-component-header/dist/index";
|
||||
@import "~@edx/frontend-component-footer/dist/footer";
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import {
|
||||
APP_INIT_ERROR, APP_READY, subscribe,
|
||||
} from '@edx/frontend-platform';
|
||||
|
||||
// Jest needs this for module resolution
|
||||
import * as app from '.'; // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
|
||||
// These need to be var not let so they get hoisted
|
||||
// and can be used by jest.mock (which is also hoisted)
|
||||
var mockRender; // eslint-disable-line no-var
|
||||
var mockCreateRoot; // eslint-disable-line no-var
|
||||
jest.mock('react-dom/client', () => {
|
||||
mockRender = jest.fn();
|
||||
mockCreateRoot = jest.fn(() => ({
|
||||
render: mockRender,
|
||||
}));
|
||||
|
||||
return ({
|
||||
createRoot: mockCreateRoot,
|
||||
});
|
||||
});
|
||||
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
APP_READY: 'app-is-ready-key',
|
||||
APP_INIT_ERROR: 'app-init-error',
|
||||
subscribe: jest.fn(),
|
||||
initialize: jest.fn(),
|
||||
mergeConfig: jest.fn(),
|
||||
getConfig: () => ({
|
||||
FAVICON_URL: 'favicon-url',
|
||||
}),
|
||||
ensureConfig: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('./components/bulk-email-tool/BulkEmailTool', () => 'Bulk Email Tool');
|
||||
jest.mock('./components/page-container/PageContainer', () => 'Page Container');
|
||||
|
||||
describe('app registry', () => {
|
||||
let getElement;
|
||||
|
||||
beforeEach(() => {
|
||||
mockCreateRoot.mockClear();
|
||||
mockRender.mockClear();
|
||||
|
||||
getElement = window.document.getElementById;
|
||||
window.document.getElementById = jest.fn(id => ({ id }));
|
||||
});
|
||||
afterAll(() => {
|
||||
window.document.getElementById = getElement;
|
||||
});
|
||||
|
||||
test('subscribe: APP_READY. links App to root element', () => {
|
||||
const callArgs = subscribe.mock.calls[0];
|
||||
expect(callArgs[0]).toEqual(APP_READY);
|
||||
callArgs[1]();
|
||||
const [rendered] = mockRender.mock.calls[0];
|
||||
expect(rendered).toMatchSnapshot();
|
||||
});
|
||||
test('subscribe: APP_INIT_ERROR. snapshot: displays an ErrorPage to root element', () => {
|
||||
const callArgs = subscribe.mock.calls[1];
|
||||
expect(callArgs[0]).toEqual(APP_INIT_ERROR);
|
||||
const error = { message: 'test-error-message' };
|
||||
callArgs[1](error);
|
||||
const [rendered] = mockRender.mock.calls[0];
|
||||
expect(rendered).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,15 +1,12 @@
|
||||
# Footer Slot
|
||||
|
||||
### Slot ID: `org.openedx.frontend.layout.footer.v1`
|
||||
|
||||
### Slot ID Aliases
|
||||
* `footer_slot`
|
||||
### Slot ID: `footer_slot`
|
||||
|
||||
## Description
|
||||
|
||||
This slot is used to replace/modify/hide the footer.
|
||||
|
||||
The implementation of the `FooterSlot` component lives in [the `frontend-component-footer` repository](https://github.com/openedx/frontend-component-footer/).
|
||||
The implementation of the `FooterSlot` component lives in [the `frontend-slot-footer` repository](https://github.com/openedx/frontend-slot-footer/).
|
||||
|
||||
## Example
|
||||
|
||||
@@ -26,7 +23,7 @@ import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-frame
|
||||
|
||||
const config = {
|
||||
pluginSlots: {
|
||||
'org.openedx.frontend.layout.footer.v1': {
|
||||
footer_slot: {
|
||||
plugins: [
|
||||
{
|
||||
// Hide the default footer
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# `frontend-app-communications` Plugin Slots
|
||||
|
||||
* [`org.openedx.frontend.layout.footer.v1`](./FooterSlot/)
|
||||
* [`footer_slot`](./FooterSlot/)
|
||||
|
||||
@@ -13,7 +13,6 @@ import messages from './i18n';
|
||||
jest.mock('@edx/frontend-platform/react/hooks', () => ({
|
||||
...jest.requireActual('@edx/frontend-platform/react/hooks'),
|
||||
useTrackColorSchemeChoice: jest.fn(),
|
||||
useParagonTheme: () => [{ isThemeLoaded: true }, jest.fn()],
|
||||
}));
|
||||
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
@@ -58,6 +57,11 @@ export function initializeMockApp() {
|
||||
return { loggingService, i18nService, authService };
|
||||
}
|
||||
|
||||
jest.mock('@edx/frontend-platform/react/hooks', () => ({
|
||||
...jest.requireActual('@edx/frontend-platform/react/hooks'),
|
||||
useTrackColorSchemeChoice: jest.fn(),
|
||||
}));
|
||||
|
||||
function render(ui, options) {
|
||||
// eslint-disable-next-line react/prop-types
|
||||
function Wrapper({ children }) {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"extends": "@edx/typescript-config",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./src",
|
||||
"paths": {
|
||||
"*": ["*"]
|
||||
},
|
||||
"rootDir": ".",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["*.js", ".eslintrc.js", "src/**/*", "plugins/**/*", "jest.config.ts"],
|
||||
"exclude": ["*.js", ".eslintrc.js", "dist", "node_modules"]
|
||||
}
|
||||
@@ -22,13 +22,8 @@ const webpack5esmInteropRule = {
|
||||
},
|
||||
};
|
||||
|
||||
const rawAssetRule = {
|
||||
resourceQuery: /raw/,
|
||||
type: 'asset/source',
|
||||
};
|
||||
|
||||
const otherRules = config.module.rules;
|
||||
|
||||
config.module.rules = [rawAssetRule, webpack5esmInteropRule, ...otherRules];
|
||||
config.module.rules = [webpack5esmInteropRule, ...otherRules];
|
||||
|
||||
module.exports = config;
|
||||
|
||||
@@ -14,13 +14,8 @@ const webpack5esmInteropRule = {
|
||||
},
|
||||
};
|
||||
|
||||
const rawAssetRule = {
|
||||
resourceQuery: /raw/,
|
||||
type: 'asset/source',
|
||||
};
|
||||
|
||||
const otherRules = config.module.rules;
|
||||
|
||||
config.module.rules = [rawAssetRule, webpack5esmInteropRule, ...otherRules];
|
||||
config.module.rules = [webpack5esmInteropRule, ...otherRules];
|
||||
|
||||
module.exports = config;
|
||||
|
||||
Reference in New Issue
Block a user