Compare commits
12 Commits
open-relea
...
release/te
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c1eb6cae0 | ||
|
|
16738335d0 | ||
|
|
658b70e455 | ||
|
|
6cb174b146 | ||
|
|
143f0dcd4b | ||
|
|
ad19426aee | ||
|
|
5ab646b69c | ||
|
|
1cd02a9dfb | ||
|
|
445a2f6cd3 | ||
|
|
166b6fe7ae | ||
|
|
a6711a59dc | ||
|
|
454d3ddcdf |
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -9,9 +9,6 @@ on:
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: [18, 20]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -23,7 +20,7 @@ jobs:
|
||||
- name: Setup Nodejs
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
node-version-file: '.nvmrc'
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Validate package-lock.json changes
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,6 +5,7 @@ node_modules
|
||||
npm-debug.log
|
||||
coverage
|
||||
module.config.js
|
||||
env.config.*
|
||||
|
||||
dist/
|
||||
src/i18n/transifex_input.json
|
||||
|
||||
@@ -12,6 +12,7 @@ metadata:
|
||||
icon: "Article"
|
||||
annotations:
|
||||
openedx.org/arch-interest-groups: ""
|
||||
openedx.org/release: "master"
|
||||
spec:
|
||||
owner: group:committers-frontend
|
||||
type: "service"
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# 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
|
||||
6823
package-lock.json
generated
6823
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
@@ -16,13 +16,9 @@
|
||||
"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",
|
||||
@@ -34,8 +30,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
|
||||
"@edx/frontend-component-header": "^5.6.0",
|
||||
"@edx/frontend-platform": "^8.0.0",
|
||||
"@edx/frontend-component-footer": "^14.6.0",
|
||||
"@edx/frontend-component-header": "^6.4.0",
|
||||
"@edx/frontend-platform": "^8.3.1",
|
||||
"@edx/openedx-atlas": "^0.6.0",
|
||||
"@edx/tinymce-language-selector": "1.1.0",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
||||
@@ -43,9 +40,8 @@
|
||||
"@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.1.2",
|
||||
"@openedx/frontend-slot-footer": "^1.0.2",
|
||||
"@openedx/paragon": "^22.0.0",
|
||||
"@openedx/frontend-plugin-framework": "^1.6.0",
|
||||
"@openedx/paragon": "^22.16.0",
|
||||
"@tinymce/tinymce-react": "3.14.0",
|
||||
"axios": "0.27.2",
|
||||
"classnames": "2.3.2",
|
||||
@@ -53,8 +49,8 @@
|
||||
"jquery": "3.6.1",
|
||||
"popper.js": "1.16.1",
|
||||
"prop-types": "15.8.1",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-redux": "7.2.9",
|
||||
"react-router": "6.15.0",
|
||||
@@ -65,13 +61,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@edx/browserslist-config": "^1.2.0",
|
||||
"@openedx/frontend-build": "14.0.3",
|
||||
"@edx/reactifex": "^2.1.1",
|
||||
"@testing-library/jest-dom": "5.16.5",
|
||||
"@testing-library/react": "12.1.5",
|
||||
"@openedx/frontend-build": "^14.3.3",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"axios-mock-adapter": "1.21.2",
|
||||
"glob": "7.2.3",
|
||||
"husky": "7.0.4",
|
||||
"jest": "29.7.0",
|
||||
"prettier": "2.8.1",
|
||||
"rosie": "2.1.0"
|
||||
|
||||
38
src/__snapshots__/index.test.jsx.snap
Normal file
38
src/__snapshots__/index.test.jsx.snap
Normal file
@@ -0,0 +1,38 @@
|
||||
// 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>
|
||||
`;
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import {
|
||||
render, screen, fireEvent, cleanup, act, initializeMockApp,
|
||||
render, screen, fireEvent, cleanup, initializeMockApp,
|
||||
} from '../../../../setupTest';
|
||||
import { BulkEmailProvider } from '../../bulk-email-context';
|
||||
import BulkEmailContentHistory from '../BulkEmailContentHistory';
|
||||
@@ -41,107 +41,99 @@ describe('BulkEmailContentHistory component', () => {
|
||||
});
|
||||
|
||||
test('renders a table when the button is pressed and data is returned', async () => {
|
||||
await act(async () => {
|
||||
const emailHistoryData = buildEmailContentHistoryData(1);
|
||||
getSentEmailHistory.mockImplementation(() => emailHistoryData);
|
||||
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 () => {
|
||||
await act(async () => {
|
||||
const emailHistoryData = buildEmailContentHistoryData(1);
|
||||
getSentEmailHistory.mockImplementation(() => emailHistoryData);
|
||||
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 () => {
|
||||
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();
|
||||
});
|
||||
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 () => {
|
||||
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();
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import {
|
||||
render, screen, fireEvent, cleanup, act, initializeMockApp,
|
||||
render, screen, fireEvent, cleanup, initializeMockApp,
|
||||
} from '../../../../setupTest';
|
||||
import BulkEmailTaskHistory from '../BulkEmailTaskHistory';
|
||||
import { getEmailTaskHistory } from '../data/api';
|
||||
@@ -32,72 +32,66 @@ describe('BulkEmailTaskHistory component', () => {
|
||||
});
|
||||
|
||||
test('renders a table properly when the button is pressed and data is returned', async () => {
|
||||
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();
|
||||
});
|
||||
// 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 () => {
|
||||
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();
|
||||
});
|
||||
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 () => {
|
||||
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();
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 '@openedx/frontend-slot-footer';
|
||||
import { FooterSlot } from '@edx/frontend-component-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 {
|
||||
act, cleanup, initializeMockApp, render, screen,
|
||||
cleanup, initializeMockApp, render, screen,
|
||||
} from '../../../setupTest';
|
||||
|
||||
import PageContainer from '../PageContainer';
|
||||
@@ -32,36 +32,32 @@ describe('PageContainer', () => {
|
||||
afterEach(cleanup);
|
||||
|
||||
test('PageContainer renders properly when given course metadata', async () => {
|
||||
await act(async () => {
|
||||
const cohorts = { cohorts: [Factory.build('cohort'), Factory.build('cohort')] };
|
||||
const courseMetadata = Factory.build('courseMetadata');
|
||||
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 () => {
|
||||
await act(async () => {
|
||||
const cohorts = { cohorts: [Factory.build('cohort'), Factory.build('cohort')] };
|
||||
const courseMetadata = Factory.build('courseMetadata');
|
||||
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,7 +5,8 @@ import {
|
||||
APP_INIT_ERROR, APP_READY, subscribe, initialize, mergeConfig, getConfig,
|
||||
} from '@edx/frontend-platform';
|
||||
import { AppProvider, AuthenticatedPageRoute, ErrorPage } from '@edx/frontend-platform/react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
@@ -16,30 +17,39 @@ import BulkEmailTool from './components/bulk-email-tool';
|
||||
import PageContainer from './components/page-container/PageContainer';
|
||||
|
||||
subscribe(APP_READY, () => {
|
||||
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>
|
||||
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>
|
||||
)}
|
||||
/>
|
||||
</Routes>
|
||||
</AppProvider>,
|
||||
document.getElementById('root'),
|
||||
/>
|
||||
</Routes>
|
||||
</AppProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
});
|
||||
|
||||
subscribe(APP_INIT_ERROR, (error) => {
|
||||
ReactDOM.render(<ErrorPage message={error.message} />, document.getElementById('root'));
|
||||
const root = createRoot(document.getElementById('root'));
|
||||
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<ErrorPage message={error.message} />
|
||||
</StrictMode>,
|
||||
);
|
||||
});
|
||||
|
||||
initialize({
|
||||
|
||||
67
src/index.test.jsx
Normal file
67
src/index.test.jsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import {
|
||||
APP_INIT_ERROR, APP_READY, subscribe,
|
||||
} from '@edx/frontend-platform';
|
||||
|
||||
// Jest needs this for module resolution
|
||||
import * as app from '.'; // eslint-disable-line 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,12 +1,15 @@
|
||||
# Footer Slot
|
||||
|
||||
### Slot ID: `footer_slot`
|
||||
### Slot ID: `org.openedx.frontend.layout.footer.v1`
|
||||
|
||||
### Slot ID Aliases
|
||||
* `footer_slot`
|
||||
|
||||
## Description
|
||||
|
||||
This slot is used to replace/modify/hide the footer.
|
||||
|
||||
The implementation of the `FooterSlot` component lives in [the `frontend-slot-footer` repository](https://github.com/openedx/frontend-slot-footer/).
|
||||
The implementation of the `FooterSlot` component lives in [the `frontend-component-footer` repository](https://github.com/openedx/frontend-component-footer/).
|
||||
|
||||
## Example
|
||||
|
||||
@@ -23,7 +26,7 @@ import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-frame
|
||||
|
||||
const config = {
|
||||
pluginSlots: {
|
||||
footer_slot: {
|
||||
'org.openedx.frontend.layout.footer.v1': {
|
||||
plugins: [
|
||||
{
|
||||
// Hide the default footer
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# `frontend-app-communications` Plugin Slots
|
||||
|
||||
* [`footer_slot`](./FooterSlot/)
|
||||
* [`org.openedx.frontend.layout.footer.v1`](./FooterSlot/)
|
||||
|
||||
Reference in New Issue
Block a user