Compare commits

..

5 Commits

Author SHA1 Message Date
Asad Ali
1999f406fe fix: convert notification banner to text if accounts url is not set (#362) (#385)
* fix: convert banner to text if ACCOUNT_SETTINGS_URL is not set

* refactor: refactoring

* refactor: rename notificationsBannerLinkMessage to notificationsBannerPreferencesCenterMessage

* refactor: remove lodash usage

* refactor: remove lodash usage
2024-12-10 10:10:37 -05:00
ihor-romaniuk
36c9763587 fix: incorrect message for locking 2024-12-07 12:41:24 +03:30
Adolfo R. Brandes
641a5e27e2 chore: remove extraneous file
Remove a file that was previously added by mistake.
2024-12-06 11:27:49 -03:00
Adolfo R. Brandes
5a9078499e fix: broken download tests
Declaring `browserslist` in package.json exposed a bug in the
download.js tests that wasn't causing failures before (but arguably,
should): one can't use arrow functions to mock constructors because
calling `new` on them doesn't work.  See the NOTE under:

https://jestjs.io/docs/es6-class-mocks#-module-factory-function-must-return-a-function
2024-12-06 11:27:49 -03:00
Adolfo R. Brandes
7ec442bc04 fix: Use browserslist-config
We were installing browserslist-config but not declaring it.  This had
the effect that webpack - and likely others - were not using it.
2024-12-06 11:27:49 -03:00
36 changed files with 8286 additions and 6253 deletions

View File

@@ -11,6 +11,9 @@ on:
jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
node: [18.15, 20]
steps:
- name: Checkout
@@ -21,7 +24,7 @@ jobs:
uses: actions/setup-node@v4
# Because of node 18 bug (https://github.com/nodejs/node/issues/47563), Pinning node version 18.15 until the next release of node
with:
node-version-file: '.nvmrc'
node-version: ${{ matrix.node }}
- name: Install dependencies
run: npm ci

32
.github/workflows/npm-publish.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Release CI
on:
push:
tags:
- "*"
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 12
- name: Install dependencies
run: npm ci
- name: Create Build
run: npm run build
- name: Release Package
env:
GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.SEMANTIC_RELEASE_NPM_TOKEN }}
run: npm semantic-release

4
.husky/pre-push Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run lint

27
.releaserc Normal file
View File

@@ -0,0 +1,27 @@
{
"branch": "master",
"tagFormat": "v${version}",
"verifyConditions": [
"@semantic-release/npm",
{
"path": "@semantic-release/github",
"assets": {
"path": "dist/*"
}
}
],
"analyzeCommits": "@semantic-release/commit-analyzer",
"generateNotes": "@semantic-release/release-notes-generator",
"prepare": "@semantic-release/npm",
"publish": [
"@semantic-release/npm",
{
"path": "@semantic-release/github",
"assets": {
"path": "dist/*"
}
}
],
"success": [],
"fail": []
}

View File

@@ -13,9 +13,7 @@ metadata:
- url: "https://ora-grading.stage.edx.org"
title: "Stage Site"
icon: "Web"
annotations:
openedx.org/release: "master"
spec:
owner: "user:codewithemad"
owner: "group:openedx-unmaintained"
type: 'website'
lifecycle: 'production'

9
openedx.yaml Normal file
View 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
tags:
- frontend-app
- masters
oeps:
oep-2: true # Repository metadata
openedx-release: {ref: master}

14108
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,9 +16,9 @@
"lint-fix": "fedx-scripts eslint --fix --ext .jsx,.js src/",
"semantic-release": "semantic-release",
"start": "fedx-scripts webpack-dev-server --progress",
"dev": "PUBLIC_PATH=/ora-grading/ MFE_CONFIG_API_URL='http://localhost:8000/api/mfe_config/v1' fedx-scripts webpack-dev-server --progress --host apps.local.openedx.io",
"test": "TZ=GMT fedx-scripts jest --coverage --passWithNoTests",
"watch-tests": "jest --watch"
"watch-tests": "jest --watch",
"prepare": "husky install"
},
"author": "edX",
"license": "AGPL-3.0",
@@ -28,17 +28,16 @@
},
"dependencies": {
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/frontend-component-footer": "^14.6.0",
"@edx/frontend-component-header": "^6.2.0",
"@edx/frontend-platform": "^8.3.1",
"@edx/frontend-component-header": "^5.6.0",
"@edx/frontend-platform": "8.0.0",
"@edx/openedx-atlas": "^0.6.0",
"@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-brands-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/react-fontawesome": "^0.2.0",
"@openedx/paragon": "^22.16.0",
"@openedx/frontend-slot-footer": "^1.0.2",
"@openedx/paragon": "21.11.3",
"@redux-beacon/segment": "^1.1.0",
"@redux-devtools/extension": "3.0.0",
"@reduxjs/toolkit": "^1.6.1",
"@testing-library/user-event": "^14.0.0",
"@zip.js/zip.js": "^2.4.6",
@@ -56,17 +55,18 @@
"moment": "^2.29.3",
"prop-types": "15.8.1",
"query-string": "7.1.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-helmet": "^6.1.0",
"react-intl": "6.4.7",
"react-pdf": "^7.0.0",
"react-pdf": "^5.5.0",
"react-redux": "^7.2.9",
"react-router": "6.21.3",
"react-router-dom": "6.21.3",
"react-router-redux": "^5.0.0-alpha.9",
"redux": "4.2.1",
"redux-beacon": "^2.1.0",
"redux-devtools-extension": "2.13.9",
"redux-logger": "3.0.6",
"redux-thunk": "2.4.2",
"regenerator-runtime": "^0.14.0",
@@ -76,20 +76,22 @@
},
"devDependencies": {
"@edx/browserslist-config": "^1.3.0",
"@edx/react-unit-test-utils": "^4.0.0",
"@edx/react-unit-test-utils": "3.0.0",
"@edx/reactifex": "^2.1.1",
"@openedx/frontend-build": "^14.3.3",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@openedx/frontend-build": "14.0.3",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "12.1.5",
"axios-mock-adapter": "^1.20.0",
"fetch-mock": "^9.11.0",
"husky": "^7.0.0",
"identity-obj-proxy": "^3.0.0",
"jest": "29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-expect-message": "^1.0.2",
"react-dev-utils": "^12.0.1",
"react-test-renderer": "^18.3.1",
"react-test-renderer": "^17.0.2",
"reactifex": "1.1.1",
"redux-mock-store": "^1.5.5"
"redux-mock-store": "^1.5.4",
"semantic-release": "^19.0.3"
}
}

View File

@@ -3,12 +3,13 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import { FooterSlot } from '@edx/frontend-component-footer';
import FooterSlot from '@openedx/frontend-slot-footer';
import { LearningHeader as Header } from '@edx/frontend-component-header';
import { selectors } from 'data/redux';
import DemoWarning from 'containers/DemoWarning';
import CTA from 'containers/CTA';
import NotificationsBanner from 'containers/NotificationsBanner';
import ListView from 'containers/ListView';
@@ -26,6 +27,7 @@ export const App = ({ courseMetadata, isEnabled }) => (
data-testid="header"
/>
{!isEnabled && <DemoWarning />}
<CTA />
<NotificationsBanner />
<main data-testid="main">
<ListView />

View File

@@ -10,7 +10,6 @@ $fa-font-path: "~font-awesome/fonts";
$input-focus-box-shadow: $input-box-shadow; // hack to get upgrade to paragon 4.0.0 to work
@import "~@edx/frontend-component-footer/dist/_footer";
@import "~@edx/frontend-component-header/dist/index";
#root {
display: flex;

View File

@@ -15,9 +15,10 @@ jest.mock('data/redux', () => ({
jest.mock('@edx/frontend-component-header', () => ({
LearningHeader: 'Header',
}));
jest.mock('@edx/frontend-component-footer', () => ({ FooterSlot: 'FooterSlot' }));
jest.mock('@edx/frontend-component-footer', () => ({ FooterSlot: 'Footer' }));
jest.mock('containers/DemoWarning', () => 'DemoWarning');
jest.mock('containers/CTA', () => 'CTA');
jest.mock('containers/ListView', () => 'ListView');
jest.mock('components/Head', () => 'Head');

View File

@@ -11,6 +11,7 @@ exports[`App router component snapshot: disabled (show demo warning) 1`] = `
data-testid="header"
/>
<DemoWarning />
<CTA />
<NotificationsBanner />
<main
data-testid="main"
@@ -32,6 +33,7 @@ exports[`App router component snapshot: enabled 1`] = `
courseTitle="course-title"
data-testid="header"
/>
<CTA />
<NotificationsBanner />
<main
data-testid="main"

View File

@@ -1,28 +1,24 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`app registry subscribe: APP_INIT_ERROR. snapshot: displays an ErrorPage to root element 1`] = `
<React Strict Mode>
<ErrorPage
message="test-error-message"
/>
</React Strict Mode>
<ErrorPage
message="test-error-message"
/>
`;
exports[`app registry subscribe: APP_READY. links App to root element 1`] = `
<React Strict Mode>
<AppProvider
store={
{
"dispatch": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
Symbol(Symbol.observable): [Function],
}
<AppProvider
store={
{
"dispatch": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
Symbol(Symbol.observable): [Function],
}
wrapWithRouter={false}
>
<App />
</AppProvider>
</React Strict Mode>
}
wrapWithRouter={false}
>
<App />
</AppProvider>
`;

View File

@@ -1,15 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Document, Page, pdfjs } from 'react-pdf';
import { pdfjs, Document, Page } from 'react-pdf';
import {
Icon, Form, ActionRow, IconButton,
} from '@openedx/paragon';
import { ChevronLeft, ChevronRight } from '@openedx/paragon/icons';
import pdfjsWorker from 'react-pdf/dist/esm/pdf.worker.entry';
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
import { rendererHooks } from './pdfHooks';
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.js`;
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;
/**
* <PDFRenderer />

View File

@@ -1,11 +1,16 @@
import { useState, useRef } from 'react';
import { pdfjs } from 'react-pdf';
import pdfjsWorker from 'react-pdf/dist/esm/pdf.worker.entry';
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
import { ErrorStatuses } from 'data/constants/requests';
import { StrictDict } from 'utils';
import * as module from './pdfHooks';
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;
export const errors = StrictDict({
missingPDF: 'MissingPDFException',
});

View File

@@ -0,0 +1,11 @@
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import { CTA } from '.';
describe('CTA component', () => {
test('snapshots', () => {
const el = shallow(<CTA hide />);
expect(el.snapshot).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CTA component snapshots 1`] = `
<PageBanner>
<span>
<FormattedMessage
defaultMessage="Thanks for using the new ORA staff grading experience. "
description="Thank user for using ora and ask for feed back"
id="ora-grading.CTA.feedbackMessage"
/>
<Hyperlink
destination="https://docs.google.com/forms/d/1Hu1rgJcCHl5_EtDb5Up3hiZ40sSUtkZQfRHJ3fWOvfQ/edit"
isInline={true}
showLaunchIcon={false}
target="_blank"
variant="muted"
>
<FormattedMessage
defaultMessage="Provide some feedback"
description="placeholder for the feedback anchor link"
id="ora-grading.CTA.linkMessage"
/>
</Hyperlink>
<FormattedMessage
defaultMessage=" and let us know what you think!"
description="inform user to provide feedback"
id="ora-grading.CTA.letUsKnowMessage"
/>
</span>
</PageBanner>
`;

View File

@@ -0,0 +1,29 @@
import React from 'react';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { PageBanner, Hyperlink } from '@openedx/paragon';
import messages from './messages';
/**
* <CTA />
*/
export const CTA = () => (
<PageBanner>
<span>
<FormattedMessage {...messages.ctaFeedbackMessage} />
<Hyperlink
isInline
variant="muted"
destination="https://docs.google.com/forms/d/1Hu1rgJcCHl5_EtDb5Up3hiZ40sSUtkZQfRHJ3fWOvfQ/edit"
target="_blank"
showLaunchIcon={false}
>
<FormattedMessage {...messages.ctaLinkMessage} />
</Hyperlink>
<FormattedMessage {...messages.ctaLetUsKnowMessage} />
</span>
</PageBanner>
);
export default CTA;

View File

@@ -0,0 +1,23 @@
/* eslint-disable quotes */
import { defineMessages } from '@edx/frontend-platform/i18n';
import { StrictDict } from 'utils';
const messages = defineMessages({
ctaFeedbackMessage: {
id: 'ora-grading.CTA.feedbackMessage',
defaultMessage: 'Thanks for using the new ORA staff grading experience. ',
description: 'Thank user for using ora and ask for feed back',
},
ctaLinkMessage: {
id: 'ora-grading.CTA.linkMessage',
defaultMessage: 'Provide some feedback',
description: 'placeholder for the feedback anchor link',
},
ctaLetUsKnowMessage: {
id: 'ora-grading.CTA.letUsKnowMessage',
defaultMessage: ' and let us know what you think!',
description: 'inform user to provide feedback',
},
});
export default StrictDict(messages);

View File

@@ -36,13 +36,12 @@ export class RadioCriterion extends React.Component {
<Form.RadioSet name={config.name} value={data}>
{config.options.map((option) => (
<Form.Radio
className="criteria-option align-items-center"
className="criteria-option"
key={option.name}
value={option.name}
description={intl.formatMessage(messages.optionPoints, { points: option.points })}
onChange={this.onChange}
disabled={!isGrading}
style={{ flexShrink: 0 }}
>
{option.label}
</Form.Radio>

View File

@@ -6,31 +6,21 @@ exports[`Radio Criterion Container snapshot is grading 1`] = `
value="selected radio option"
>
<Form.Radio
className="criteria-option align-items-center"
className="criteria-option"
description="1 points"
disabled={false}
key="option name"
onChange={[Function]}
style={
{
"flexShrink": 0,
}
}
value="option name"
>
this label
</Form.Radio>
<Form.Radio
className="criteria-option align-items-center"
className="criteria-option"
description="2 points"
disabled={false}
key="option name 2"
onChange={[Function]}
style={
{
"flexShrink": 0,
}
}
value="option name 2"
>
this label 2
@@ -44,31 +34,21 @@ exports[`Radio Criterion Container snapshot is not grading 1`] = `
value="selected radio option"
>
<Form.Radio
className="criteria-option align-items-center"
className="criteria-option"
description="1 points"
disabled={true}
key="option name"
onChange={[Function]}
style={
{
"flexShrink": 0,
}
}
value="option name"
>
this label
</Form.Radio>
<Form.Radio
className="criteria-option align-items-center"
className="criteria-option"
description="2 points"
disabled={true}
key="option name 2"
onChange={[Function]}
style={
{
"flexShrink": 0,
}
}
value="option name 2"
>
this label 2
@@ -82,31 +62,21 @@ exports[`Radio Criterion Container snapshot radio contain invalid response 1`] =
value="selected radio option"
>
<Form.Radio
className="criteria-option align-items-center"
className="criteria-option"
description="1 points"
disabled={false}
key="option name"
onChange={[Function]}
style={
{
"flexShrink": 0,
}
}
value="option name"
>
this label
</Form.Radio>
<Form.Radio
className="criteria-option align-items-center"
className="criteria-option"
description="2 points"
disabled={false}
key="option name 2"
onChange={[Function]}
style={
{
"flexShrink": 0,
}
}
value="option name 2"
>
this label 2

View File

@@ -11,7 +11,7 @@ exports[`NotificationsBanner component snapshots with ACCOUNT_SETTINGS_URL 1`] =
id="ora-grading.NotificationsBanner.Message"
/>
<Hyperlink
destination="http://localhost:1997/#notifications"
destination="http://localhost:1997/notifications"
isInline={true}
rel="noopener noreferrer"
showLaunchIcon={false}

View File

@@ -21,7 +21,7 @@ export const NotificationsBanner = () => (
<Hyperlink
isInline
variant="muted"
destination={`${getConfig().ACCOUNT_SETTINGS_URL}/#notifications`}
destination={`${getConfig().ACCOUNT_SETTINGS_URL}/notifications`}
target="_blank"
rel="noopener noreferrer"
showLaunchIcon={false}

View File

@@ -2,11 +2,14 @@
.review-modal-body {
background-color: $gray-300 !important;
overflow: auto !important;
padding: inherit;
& > div.pgn__modal-body-content .row {
& > div.pgn__modal-body-content {
height: 100%;
.row {
height: 100%;
}
}
.content-block {
@@ -37,4 +40,4 @@
width: 100%;
}
}
}
}

View File

@@ -19,19 +19,15 @@
}
}
.criteria-option {
display: flex;
width: 100%;
> div {
display: flex;
align-items: flex-start;
justify-content: space-between;
display: inline;
width: 100%;
.pgn__form-label {
display: inline-flex;
padding-right: 10px;
}
.pgn__form-control-description {
white-space: nowrap;
float: right;
}
}
}
@@ -46,10 +42,6 @@
.help-popover-option {
margin-bottom: map-get($spacers, 1);
}
.popover-body {
max-height: 100vh;
overflow-y: auto !important;
}
}
@@ -88,4 +80,4 @@
.grading-rubric-card {
margin-left: 0 !important;
}
}
}

View File

@@ -1,5 +1,4 @@
import { getConfig, getPath } from '@edx/frontend-platform';
import { getConfig } from '@edx/frontend-platform';
const publicPath = getPath(getConfig().PUBLIC_PATH);
export const routePath = () => `${publicPath}:courseId`;
export const locationId = () => decodeURIComponent(window.location.pathname).replace(publicPath, '');
export const routePath = () => `${getConfig().PUBLIC_PATH}:courseId`;
export const locationId = () => decodeURIComponent(window.location.pathname).replace(getConfig().PUBLIC_PATH, '');

View File

@@ -6,7 +6,6 @@ jest.unmock('./app');
jest.mock('@edx/frontend-platform', () => {
const PUBLIC_PATH = '/test-public-path/';
return {
...jest.requireActual('@edx/frontend-platform'),
getConfig: () => ({ PUBLIC_PATH }),
PUBLIC_PATH,
};

View File

@@ -182,11 +182,11 @@ const grading = createSlice({
const gradeData = {
...state.gradeData,
...(payload && { [submissionUUID]: payload.gradeData }),
...(payload && { [submissionUUID]: payload.submissionStatus.gradeData }),
};
const { gradeStatus } = payload || state.current;
const lockStatus = payload ? payload.lockStatus : lockStatuses.unlocked;
const { gradeStatus } = payload ? payload.submissionStatus : state.current;
const lockStatus = payload ? payload.submissionStatus.lockStatus : lockStatuses.unlocked;
return {
...state,

View File

@@ -250,7 +250,7 @@ describe('app reducer', () => {
});
describe('stopGrading', () => {
let output;
const args = { gradeData: testData, lockStatus, gradeStatus };
const args = { submissionStatus: { gradeData: testData, lockStatus, gradeStatus } };
describe('resulting state', () => {
test('gradingData: deletes current data', () => {
output = reducer(testState, actions.stopGrading());

View File

@@ -1,4 +1,4 @@
import sortBy from 'lodash/sortBy';
import _ from 'lodash';
import { createSelector } from 'reselect';
import { StrictDict } from 'utils';
@@ -22,7 +22,7 @@ export const listData = createSelector(
const gradingStatus = (lockStatus === lockStatuses.unlocked ? gradeStatus : lockStatus);
return { gradingStatus, ...rest };
});
return sortBy(
return _.sortBy(
submissionList,
['submissionDate'],
);

View File

@@ -1,6 +1,6 @@
import * as redux from 'redux';
import thunkMiddleware from 'redux-thunk';
import { composeWithDevTools } from '@redux-devtools/extension';
import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProduction';
import { createLogger } from 'redux-logger';
import apiTestUtils from 'data/services/lms/fakeData/testUtils';

View File

@@ -1,6 +1,6 @@
import { applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { composeWithDevTools } from '@redux-devtools/extension';
import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProduction';
import { createLogger } from 'redux-logger';
import rootReducer, { actions, selectors } from 'data/redux';
@@ -22,7 +22,7 @@ jest.mock('redux', () => ({
applyMiddleware: (...middleware) => ({ applied: middleware }),
createStore: (reducer, middleware) => ({ reducer, middleware }),
}));
jest.mock('@redux-devtools/extension', () => ({
jest.mock('redux-devtools-extension/logOnlyInProduction', () => ({
composeWithDevTools: (middleware) => ({ withDevTools: middleware }),
}));

View File

@@ -2,8 +2,8 @@
import 'core-js/stable';
import 'regenerator-runtime/runtime';
import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import React from 'react';
import ReactDOM from 'react-dom';
import { AppProvider, ErrorPage } from '@edx/frontend-platform/react';
import store from 'data/store';
@@ -20,24 +20,18 @@ import messages from './i18n';
import App from './App';
subscribe(APP_READY, () => {
const root = createRoot(document.getElementById('root'));
root.render(
<StrictMode>
<AppProvider store={store} wrapWithRouter={false}>
<App />
</AppProvider>
</StrictMode>,
ReactDOM.render(
<AppProvider store={store} wrapWithRouter={false}>
<App />
</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'),
);
});

View File

@@ -1,3 +1,5 @@
import { render } from 'react-dom';
import {
APP_INIT_ERROR,
APP_READY,
@@ -9,24 +11,8 @@ import {
import messages from './i18n';
import * as app from '.';
// 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('react', () => ({
...jest.requireActual('react'),
StrictMode: 'React Strict Mode',
jest.mock('react-dom', () => ({
render: jest.fn(),
}));
jest.mock('@edx/frontend-component-footer', () => ({
@@ -53,7 +39,7 @@ describe('app registry', () => {
let getElement;
beforeEach(() => {
mockRender.mockClear();
render.mockClear();
getElement = window.document.getElementById;
window.document.getElementById = jest.fn(id => ({ id }));
});
@@ -65,16 +51,18 @@ describe('app registry', () => {
const callArgs = subscribe.mock.calls[0];
expect(callArgs[0]).toEqual(APP_READY);
callArgs[1]();
const [rendered] = mockRender.mock.calls[0];
const [rendered, target] = render.mock.calls[0];
expect(rendered).toMatchSnapshot();
expect(target).toEqual(document.getElementById('root'));
});
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];
const [rendered, target] = render.mock.calls[0];
expect(rendered).toMatchSnapshot();
expect(target).toEqual(document.getElementById('root'));
});
test('initialize is called with footerMessages and requireAuthenticatedUser', () => {
expect(initialize).toHaveBeenCalledTimes(1);

View File

@@ -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

View File

@@ -1,3 +1,3 @@
# `frontend-app-ora-grading` Plugin Slots
* [`org.openedx.frontend.layout.footer.v1`](./FooterSlot/)
* [`footer_slot`](./FooterSlot/)