Compare commits
13 Commits
open-relea
...
teak-desig
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8e5337b7b | ||
|
|
88bf980546 | ||
|
|
bad12462f5 | ||
|
|
ec915f622b | ||
|
|
60da5eafc4 | ||
|
|
05cf174335 | ||
|
|
ff72dab001 | ||
|
|
c38887ec2b | ||
|
|
58aa512f47 | ||
|
|
62a5c11f52 | ||
|
|
3ef8515891 | ||
|
|
3cc39d83c4 | ||
|
|
af6cd1853c |
2
.env
2
.env
@@ -22,3 +22,5 @@ USER_INFO_COOKIE_NAME=''
|
||||
SUPPORT_URL=''
|
||||
LEARNER_FEEDBACK_URL=''
|
||||
STAFF_FEEDBACK_URL=''
|
||||
# Fallback in local style files
|
||||
PARAGON_THEME_URLS={}
|
||||
|
||||
@@ -23,3 +23,5 @@ USER_INFO_COOKIE_NAME='edx-user-info'
|
||||
SUPPORT_URL='https://support.edx.org'
|
||||
LEARNER_FEEDBACK_URL=''
|
||||
STAFF_FEEDBACK_URL=''
|
||||
# Fallback in local style files
|
||||
PARAGON_THEME_URLS={}
|
||||
|
||||
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
|
||||
@@ -21,7 +18,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
@@ -6,6 +6,7 @@ node_modules
|
||||
npm-debug.log
|
||||
coverage
|
||||
module.config.js
|
||||
env.config.*
|
||||
|
||||
dist/
|
||||
src/i18n/transifex_input.json
|
||||
|
||||
@@ -76,7 +76,7 @@ How to Contribute
|
||||
|
||||
Details about how to become a contributor to the Open edX project may be found in the wiki at `How to contribute`_
|
||||
|
||||
.. _How to contribute: https://edx.readthedocs.io/projects/edx-developer-guide/en/latest/process/index.html
|
||||
.. _How to contribute: https://docs.openedx.org/en/latest/developers/references/developer_guide/process/index.html
|
||||
|
||||
PR description template should be automatically applied if you are sending PR from github interface; otherwise you
|
||||
can find it it at `PULL_REQUEST_TEMPLATE.md <https://github.com/openedx/frontend-app-discussions/blob/master/.github/pull_request_template.md>`_
|
||||
@@ -125,4 +125,4 @@ Please see `edx/frontend-platform's i18n module <https://edx.github.io/frontend-
|
||||
Reporting Security Issues
|
||||
=========================
|
||||
|
||||
Please do not report security issues in public. Please email security@openedx.org.
|
||||
Please do not report security issues in public. Please email security@openedx.org.
|
||||
|
||||
@@ -12,6 +12,7 @@ metadata:
|
||||
icon: "Web"
|
||||
annotations:
|
||||
openedx.org/arch-interest-groups: ""
|
||||
openedx.org/release: "master"
|
||||
spec:
|
||||
owner: group:edx-infinity
|
||||
type: 'website'
|
||||
|
||||
11
openedx.yaml
11
openedx.yaml
@@ -1,11 +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
|
||||
|
||||
nick: tmpa
|
||||
oeps: {}
|
||||
owner: edx/arch-team
|
||||
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
|
||||
10597
package-lock.json
generated
10597
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
@@ -16,13 +16,9 @@
|
||||
"lint:fix": "fedx-scripts eslint --ext .js --ext .jsx . --fix",
|
||||
"snapshot": "fedx-scripts jest --updateSnapshot",
|
||||
"start": "fedx-scripts webpack-dev-server --progress",
|
||||
"dev": "PUBLIC_PATH=/discussions/ MFE_CONFIG_API_URL='http://localhost:8000/api/mfe_config/v1' fedx-scripts webpack-dev-server --progress --host apps.local.openedx.io",
|
||||
"test": "fedx-scripts jest --coverage --passWithNoTests"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "npm run lint"
|
||||
}
|
||||
},
|
||||
"author": "edX",
|
||||
"license": "AGPL-3.0",
|
||||
"homepage": "https://github.com/openedx/frontend-app-discussions#readme",
|
||||
@@ -34,13 +30,13 @@
|
||||
},
|
||||
"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.2.0",
|
||||
"@edx/frontend-platform": "^8.3.8",
|
||||
"@edx/openedx-atlas": "^0.6.0",
|
||||
"@openedx/frontend-slot-footer": "^1.0.2",
|
||||
"@openedx/paragon": "^22.1.1",
|
||||
"@openedx/paragon": "^23.4.5",
|
||||
"@reduxjs/toolkit": "1.9.7",
|
||||
"@tinymce/tinymce-react": "3.13.1",
|
||||
"@tinymce/tinymce-react": "5.1.1",
|
||||
"babel-polyfill": "6.26.0",
|
||||
"classnames": "2.5.1",
|
||||
"core-js": "3.21.1",
|
||||
@@ -49,8 +45,8 @@
|
||||
"lodash.snakecase": "4.1.1",
|
||||
"prop-types": "15.8.1",
|
||||
"raw-loader": "4.0.2",
|
||||
"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.18.0",
|
||||
@@ -64,16 +60,15 @@
|
||||
"devDependencies": {
|
||||
"@edx/browserslist-config": "1.2.0",
|
||||
"@edx/reactifex": "1.1.0",
|
||||
"@openedx/frontend-build": "14.0.3",
|
||||
"@openedx/frontend-build": "^14.3.3",
|
||||
"@testing-library/jest-dom": "5.17.0",
|
||||
"@testing-library/react": "12.1.5",
|
||||
"@testing-library/react": "14.3.1",
|
||||
"@testing-library/user-event": "13.5.0",
|
||||
"axios": "^0.28.0",
|
||||
"axios-mock-adapter": "1.22.0",
|
||||
"babel-plugin-react-intl": "8.2.25",
|
||||
"eslint-plugin-simple-import-sort": "7.0.0",
|
||||
"glob": "7.2.0",
|
||||
"husky": "7.0.4",
|
||||
"jest": "29.7.0",
|
||||
"rosie": "2.1.1"
|
||||
}
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
@import "~@edx/brand/paragon/fonts.scss";
|
||||
@import "~@edx/brand/paragon/variables.scss";
|
||||
@import "~@openedx/paragon/scss/core/core.scss";
|
||||
@import "~@edx/brand/paragon/overrides.scss";
|
||||
|
||||
$fa-font-path: "~font-awesome/fonts";
|
||||
@import "~font-awesome/scss/font-awesome";
|
||||
|
||||
.course-tabs-navigation {
|
||||
border-bottom: solid 1px #eaeaea;
|
||||
|
||||
.nav a,
|
||||
.nav button {
|
||||
&:hover {
|
||||
background-color: $light-400;
|
||||
background-color: var(--pgn-color-light-400);
|
||||
}
|
||||
}
|
||||
|
||||
.nav a {
|
||||
&:not(.active):hover {
|
||||
background-color: $light-400;
|
||||
background-color: var(--pgn-color-light-400);
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
@@ -30,7 +22,7 @@ $fa-font-path: "~font-awesome/fonts";
|
||||
.nav-link {
|
||||
border-bottom: 4px solid transparent;
|
||||
border-top: 4px solid transparent;
|
||||
color: $gray-700;
|
||||
color: var(--pgn-color-gray-700);
|
||||
|
||||
// temporary until we can remove .btn class from dropdowns
|
||||
border-left: 0;
|
||||
@@ -40,9 +32,9 @@ $fa-font-path: "~font-awesome/fonts";
|
||||
&:hover,
|
||||
&:focus,
|
||||
&.active {
|
||||
font-weight: $font-weight-normal;
|
||||
color: $primary-500;
|
||||
border-bottom-color: $primary-500;
|
||||
font-weight: var(--pgn-typography-font-weight-normal);
|
||||
color: var(--pgn-color-primary-500);
|
||||
border-bottom-color: var(--pgn-color-primary-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,8 +106,8 @@ describe('HoverCard', () => {
|
||||
});
|
||||
|
||||
test('it should have hover card on post', async () => {
|
||||
await waitFor(() => renderComponent(discussionPostId));
|
||||
const post = screen.getByTestId('post-thread-1');
|
||||
renderComponent(discussionPostId);
|
||||
const post = await screen.findByTestId('post-thread-1');
|
||||
expect(within(post).getByTestId('hover-card-thread-1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
||||
@@ -33,14 +33,11 @@ import { ContentSelectors } from './constants';
|
||||
import {
|
||||
selectAreThreadsFiltered,
|
||||
selectEnableInContext,
|
||||
selectIsCourseAdmin,
|
||||
selectIsCourseStaff,
|
||||
selectIsPostingEnabled,
|
||||
selectIsUserLearner,
|
||||
selectPostThreadCount,
|
||||
selectUserHasModerationPrivileges,
|
||||
selectUserIsGroupTa,
|
||||
selectUserIsStaff,
|
||||
} from './selectors';
|
||||
import fetchCourseConfig from './thunks';
|
||||
|
||||
@@ -220,12 +217,9 @@ export const useCurrentDiscussionTopic = () => {
|
||||
|
||||
export const useUserPostingEnabled = () => {
|
||||
const isPostingEnabled = useSelector(selectIsPostingEnabled);
|
||||
const isUserAdmin = useSelector(selectUserIsStaff);
|
||||
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
|
||||
const isUserGroupTA = useSelector(selectUserIsGroupTa);
|
||||
const isCourseAdmin = useSelector(selectIsCourseAdmin);
|
||||
const isCourseStaff = useSelector(selectIsCourseStaff);
|
||||
const isPrivileged = isUserAdmin || userHasModerationPrivileges || isUserGroupTA || isCourseAdmin || isCourseStaff;
|
||||
const isPrivileged = userHasModerationPrivileges || isUserGroupTA;
|
||||
|
||||
return (isPostingEnabled || isPrivileged);
|
||||
};
|
||||
|
||||
@@ -19,11 +19,11 @@ const courseConfigApiUrl = getCourseConfigApiUrl();
|
||||
let store;
|
||||
let axiosMock;
|
||||
|
||||
const generateApiResponse = (isPostingEnabled, isCourseAdmin = false) => ({
|
||||
const generateApiResponse = (isPostingEnabled, hasModerationPrivileges = false) => ({
|
||||
isPostingEnabled,
|
||||
hasModerationPrivileges: false,
|
||||
hasModerationPrivileges,
|
||||
isGroupTa: false,
|
||||
isCourseAdmin,
|
||||
isCourseAdmin: false,
|
||||
isCourseStaff: false,
|
||||
isUserAdmin: false,
|
||||
});
|
||||
@@ -160,7 +160,7 @@ describe('Hooks', () => {
|
||||
expect(queryByText('false')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('when posting is not disabled and Role is not Learner return true', async () => {
|
||||
test('when posting is disabled and Role is not Learner return true', async () => {
|
||||
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`)
|
||||
.reply(200, generateApiResponse(false, true));
|
||||
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { Context as ResponsiveContext } from 'react-responsive';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
@@ -85,7 +84,7 @@ describe('DiscussionSidebar', () => {
|
||||
},
|
||||
})]);
|
||||
renderComponent();
|
||||
await act(async () => expect(await screen.findAllByText('Thread by other users')).toBeTruthy());
|
||||
await screen.findAllByText('Thread by other users');
|
||||
expect(screen.queryByText('Thread by abc123')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -100,7 +99,7 @@ describe('DiscussionSidebar', () => {
|
||||
},
|
||||
})]);
|
||||
renderComponent();
|
||||
await act(async () => expect(await screen.findAllByText('Thread by other users')).toBeTruthy());
|
||||
await screen.findAllByText('Thread by other users');
|
||||
expect(screen.queryByText('Thread by abc123')).not.toBeInTheDocument();
|
||||
expect(container.querySelectorAll('.discussion-post')).toHaveLength(postCount);
|
||||
});
|
||||
|
||||
@@ -27,7 +27,7 @@ import { selectPostEditorVisible } from '../posts/data/selectors';
|
||||
import { isCourseStatusValid } from '../utils';
|
||||
import useFeedbackWrapper from './FeedbackWrapper';
|
||||
|
||||
const FooterSlot = lazy(() => import('@openedx/frontend-slot-footer'));
|
||||
const FooterSlot = lazy(() => import('@edx/frontend-component-footer').then(module => ({ default: module.FooterSlot })));
|
||||
const PostActionsBar = lazy(() => import('../posts/post-actions-bar/PostActionsBar'));
|
||||
const CourseTabsNavigation = lazy(() => import('../../components/NavigationBar/CourseTabsNavigation'));
|
||||
const LegacyBreadcrumbMenu = lazy(() => import('../navigation/breadcrumb-menu/LegacyBreadcrumbMenu'));
|
||||
|
||||
@@ -90,8 +90,7 @@ describe('DiscussionsHome', () => {
|
||||
|
||||
test('full view should hide close button', async () => {
|
||||
renderComponent(`/${courseId}/topics`);
|
||||
expect(screen.queryByText(navigationBarMessages.allTopics.defaultMessage))
|
||||
.toBeInTheDocument();
|
||||
await screen.findByText(navigationBarMessages.allTopics.defaultMessage);
|
||||
expect(screen.queryByRole('button', { name: 'Close' }))
|
||||
.not
|
||||
.toBeInTheDocument();
|
||||
@@ -144,9 +143,7 @@ describe('DiscussionsHome', () => {
|
||||
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
|
||||
await renderComponent(`/${courseId}/${searchByEndPoint}`);
|
||||
|
||||
waitFor(() => {
|
||||
expect(screen.queryByText('Add a post')).toBeInTheDocument();
|
||||
});
|
||||
await screen.findByText('Add a post');
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -170,9 +167,7 @@ describe('DiscussionsHome', () => {
|
||||
await executeThunk(fetchThreads(courseId), store.dispatch, store.getState);
|
||||
await renderComponent(`/${courseId}/${searchByEndPoint}`);
|
||||
|
||||
waitFor(() => {
|
||||
expect(screen.queryByText(result)).toBeInTheDocument();
|
||||
});
|
||||
await screen.findByText(result);
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -199,9 +194,7 @@ describe('DiscussionsHome', () => {
|
||||
await executeThunk(fetchCourseTopicsV3(courseId), store.dispatch, store.getState);
|
||||
await renderComponent(`/${courseId}/${searchByEndPoint}`);
|
||||
|
||||
waitFor(() => {
|
||||
expect(screen.queryByText('No topic selected')).toBeInTheDocument();
|
||||
});
|
||||
await screen.findByText('No topic selected');
|
||||
},
|
||||
);
|
||||
|
||||
@@ -210,9 +203,7 @@ describe('DiscussionsHome', () => {
|
||||
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
|
||||
await renderComponent(`/${courseId}/learners`);
|
||||
|
||||
waitFor(() => {
|
||||
expect(screen.queryByText('Nothing here yet')).toBeInTheDocument();
|
||||
});
|
||||
await screen.findByText('Nothing here yet');
|
||||
});
|
||||
|
||||
it('should display post editor form when click on add a post button for posts', async () => {
|
||||
@@ -235,10 +226,10 @@ describe('DiscussionsHome', () => {
|
||||
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
|
||||
await renderComponent(`/${courseId}/topics`);
|
||||
|
||||
await waitFor(() => expect(screen.queryByText('Nothing here yet')).toBeInTheDocument());
|
||||
await screen.findByText('Nothing here yet');
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.queryByText('Add a post'));
|
||||
fireEvent.click((await screen.findAllByText('Add a post'))[0]);
|
||||
});
|
||||
|
||||
await waitFor(() => expect(container.querySelector('.post-form')).toBeInTheDocument());
|
||||
@@ -247,27 +238,27 @@ describe('DiscussionsHome', () => {
|
||||
it('should display Add a post button for legacy topics view', async () => {
|
||||
await renderComponent(`/${courseId}/topics/topic-1`);
|
||||
|
||||
await waitFor(() => expect(screen.queryByText('Add a post')).toBeInTheDocument());
|
||||
await screen.findByText('Add a post');
|
||||
});
|
||||
|
||||
it('should display No post selected for legacy topics view', async () => {
|
||||
await setUpV1TopicsMockResponse();
|
||||
await renderComponent(`/${courseId}/topics/category-1-topic-1`);
|
||||
|
||||
await waitFor(() => expect(screen.queryByText('No post selected')).toBeInTheDocument());
|
||||
await screen.findByText('No post selected');
|
||||
});
|
||||
|
||||
it('should display No topic selected for legacy topics view', async () => {
|
||||
await setUpV1TopicsMockResponse();
|
||||
await renderComponent(`/${courseId}/topics`);
|
||||
|
||||
await waitFor(() => expect(screen.queryByText('No topic selected')).toBeInTheDocument());
|
||||
await screen.findByText('No topic selected');
|
||||
});
|
||||
|
||||
it('should display navigation tabs', async () => {
|
||||
renderComponent(`/${courseId}/topics`);
|
||||
|
||||
await waitFor(() => expect(screen.queryByText('Discussion')).toBeInTheDocument());
|
||||
await screen.findByText('Discussion');
|
||||
});
|
||||
|
||||
it('should display content unavailable message when the user is not enrolled in the course.', async () => {
|
||||
@@ -276,7 +267,7 @@ describe('DiscussionsHome', () => {
|
||||
|
||||
renderComponent();
|
||||
|
||||
await waitFor(() => expect(screen.queryByText('Content unavailable')).toBeInTheDocument());
|
||||
await screen.findByText('Content unavailable');
|
||||
});
|
||||
|
||||
it('should redirect to dashboard when the user clicks on the Enroll button.', async () => {
|
||||
|
||||
@@ -66,15 +66,13 @@ describe('EmptyTopics', () => {
|
||||
|
||||
test('"no topic selected" text shown when viewing topics page', async () => {
|
||||
renderComponent(`/${courseId}/topics/`);
|
||||
expect(screen.queryByText(messages.emptyTitle.defaultMessage))
|
||||
.toBeInTheDocument();
|
||||
await screen.findByText(messages.emptyTitle.defaultMessage);
|
||||
});
|
||||
|
||||
test('"no post selected" text shown when viewing a specific topic', async () => {
|
||||
await setupMockResponse();
|
||||
renderComponent(`/${courseId}/topics/ncwtopic-3/`);
|
||||
|
||||
expect(screen.queryByText(messages.noPostSelected.defaultMessage))
|
||||
.toBeInTheDocument();
|
||||
await screen.findByText(messages.noPostSelected.defaultMessage);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -283,9 +283,9 @@ describe('InContext Topic Posts View', () => {
|
||||
await setupTopicsMockResponse(0, 0, 0);
|
||||
await renderComponent({ topicId: 'test-topic', category: 'test-category' });
|
||||
|
||||
await waitFor(() => expect(within(container).queryByText('Nothing here yet')).toBeInTheDocument());
|
||||
expect(within(container).queryByText('No topic exists')).toBeInTheDocument();
|
||||
expect(within(container).queryByText('Unnamed Topic')).toBeInTheDocument();
|
||||
await within(container).findByText('Nothing here yet');
|
||||
await within(container).findByText('No topic exists');
|
||||
await within(container).findByText('Unnamed Topic');
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
fireEvent, render, screen, waitFor,
|
||||
within,
|
||||
} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
@@ -165,9 +166,10 @@ describe('InContext Topics View', () => {
|
||||
it('The subsection should have a title name, be clickable, and have the stats', async () => {
|
||||
await setupMockResponse();
|
||||
renderComponent();
|
||||
await screen.findByTestId('redux-provider');
|
||||
const subsectionObject = coursewareTopics[0].children[0];
|
||||
const subSection = await container.querySelector(`[data-subsection-id=${subsectionObject.id}]`);
|
||||
const subSectionTitle = await within(subSection).queryByText(subsectionObject.displayName);
|
||||
const subSectionTitle = await within(subSection).findByText(subsectionObject.displayName);
|
||||
const statsList = await subSection.querySelectorAll('.icon-size');
|
||||
|
||||
expect(subSectionTitle).toBeInTheDocument();
|
||||
@@ -177,11 +179,12 @@ describe('InContext Topics View', () => {
|
||||
it('Subsection names should be clickable and redirected to the units lists', async () => {
|
||||
await setupMockResponse();
|
||||
renderComponent();
|
||||
await screen.findByTestId('redux-provider');
|
||||
|
||||
const subsectionObject = coursewareTopics[0].children[0];
|
||||
const subSection = await container.querySelector(`[data-subsection-id=${subsectionObject.id}]`);
|
||||
|
||||
await act(async () => { fireEvent.click(subSection); });
|
||||
await userEvent.click(subSection);
|
||||
await waitFor(async () => {
|
||||
const backButton = await screen.getByLabelText('Back to topics list');
|
||||
const topicsList = await screen.getByRole('list');
|
||||
@@ -198,9 +201,11 @@ describe('InContext Topics View', () => {
|
||||
it('The number of units should be matched with the actual unit length.', async () => {
|
||||
await setupMockResponse();
|
||||
renderComponent();
|
||||
await screen.findByTestId('redux-provider');
|
||||
|
||||
const subSection = await container.querySelector(`[data-subsection-id=${coursewareTopics[0].children[0].id}]`);
|
||||
|
||||
await act(async () => { fireEvent.click(subSection); });
|
||||
await userEvent.click(subSection);
|
||||
await waitFor(async () => {
|
||||
const units = await container.querySelectorAll('.discussion-topic');
|
||||
|
||||
@@ -211,12 +216,14 @@ describe('InContext Topics View', () => {
|
||||
it('A unit should have a title and should be clickable', async () => {
|
||||
await setupMockResponse();
|
||||
renderComponent();
|
||||
await screen.findByTestId('redux-provider');
|
||||
|
||||
const subSectionObject = coursewareTopics[0].children[0];
|
||||
const unitObject = subSectionObject.children[0];
|
||||
|
||||
const subSection = await container.querySelector(`[data-subsection-id=${subSectionObject.id}]`);
|
||||
|
||||
await act(async () => { fireEvent.click(subSection); });
|
||||
await userEvent.click(subSection);
|
||||
await waitFor(async () => {
|
||||
const unitElement = await screen.findByText(unitObject.name);
|
||||
const unitContainer = await container.querySelector(`[data-topic-id=${unitObject.id}]`);
|
||||
|
||||
@@ -92,7 +92,7 @@ const CommentsView = ({ threadType }) => {
|
||||
variant="plain"
|
||||
block="true"
|
||||
className="card mb-4 px-0 border-0 py-10px mt-2 font-style font-weight-500
|
||||
line-height-24 text-primary-500"
|
||||
line-height-24 text-primary-500 bg-white"
|
||||
onClick={handleAddResponse}
|
||||
data-testid="add-response"
|
||||
>
|
||||
|
||||
@@ -129,7 +129,7 @@ const Reply = ({ responseId }) => {
|
||||
</div>
|
||||
<div
|
||||
className="bg-light-300 pl-4 pt-2.5 pr-2.5 pb-10px flex-fill"
|
||||
style={{ borderRadius: '0rem 0.375rem 0.375rem' }}
|
||||
style={{ borderRadius: '0rem 0.375rem 0.375rem', maxWidth: 'calc(100% - 50px)' }}
|
||||
>
|
||||
<div className="d-flex flex-row justify-content-between">
|
||||
<AuthorLabel
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'core-js/stable';
|
||||
import 'regenerator-runtime/runtime';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import React, { StrictMode } from 'react';
|
||||
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import {
|
||||
APP_INIT_ERROR, APP_READY, initialize, mergeConfig,
|
||||
@@ -17,18 +19,20 @@ import store from './store';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const rootNode = createRoot(document.getElementById('root'));
|
||||
subscribe(APP_READY, () => {
|
||||
ReactDOM.render(
|
||||
<AppProvider store={store}>
|
||||
<Head />
|
||||
<DiscussionsHome />
|
||||
</AppProvider>,
|
||||
document.getElementById('root'),
|
||||
rootNode.render(
|
||||
<StrictMode>
|
||||
<AppProvider store={store}>
|
||||
<Head />
|
||||
<DiscussionsHome />
|
||||
</AppProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
});
|
||||
|
||||
subscribe(APP_INIT_ERROR, (error) => {
|
||||
ReactDOM.render(<ErrorPage message={error.message} />, document.getElementById('root'));
|
||||
rootNode.render(<ErrorPage message={error.message} />);
|
||||
});
|
||||
|
||||
initialize({
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
@import "~@edx/brand/paragon/fonts.scss";
|
||||
@import "~@edx/brand/paragon/variables.scss";
|
||||
@import "~@openedx/paragon/scss/core/core.scss";
|
||||
@import "~@edx/brand/paragon/overrides.scss";
|
||||
@use "@openedx/paragon/styles/css/core/custom-media-breakpoints" as paragonCustomMediaBreakpoints;
|
||||
|
||||
|
||||
@import "~@edx/frontend-component-footer/dist/footer";
|
||||
@import "~@edx/frontend-component-header/dist/index";
|
||||
|
||||
$fa-font-path: "~font-awesome/fonts";
|
||||
@import "~font-awesome/scss/font-awesome";
|
||||
|
||||
body,
|
||||
#main
|
||||
@@ -28,6 +24,10 @@ body,
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
.btn-plain {
|
||||
background-color: var(--pgn-color-card-bg-base) !important;
|
||||
}
|
||||
|
||||
#post,
|
||||
#comment,
|
||||
#reply,
|
||||
@@ -41,23 +41,23 @@ body,
|
||||
}
|
||||
|
||||
.text-staff-color {
|
||||
color: $warning-700;
|
||||
color: var(--pgn-color-warning-700);
|
||||
}
|
||||
|
||||
.outline-staff-color {
|
||||
outline: $warning-700 solid 2px;
|
||||
outline: var(--pgn-color-warning-700) solid 2px;
|
||||
}
|
||||
|
||||
.text-TA-color {
|
||||
color: $success-700;
|
||||
color: var(--pgn-color-success-700);
|
||||
}
|
||||
|
||||
.outline-TA-color {
|
||||
outline: $success-700 solid 2px;
|
||||
outline: var(--pgn-color-success-700) solid 2px;
|
||||
}
|
||||
|
||||
.outline-anonymous {
|
||||
outline: $light-400 solid 2px;
|
||||
outline: var(--pgn-color-light-400) solid 2px;
|
||||
}
|
||||
|
||||
.font-size-8 {
|
||||
@@ -173,7 +173,7 @@ body,
|
||||
}
|
||||
|
||||
.learner > a:hover {
|
||||
background-color: $light-300;
|
||||
background-color: var(--pgn-color-light-300);
|
||||
}
|
||||
|
||||
.py-10px {
|
||||
@@ -252,12 +252,12 @@ header {
|
||||
}
|
||||
|
||||
.border-light-400-2 {
|
||||
border: 2px solid $light-400 !important;
|
||||
border: 2px solid var(--pgn-color-light-400) !important;
|
||||
border-width: 2px !important;
|
||||
}
|
||||
|
||||
.border-primary-500-2 {
|
||||
border: 2px solid $primary-500 !important;
|
||||
border: 2px solid var(--pgn-color-primary-500) !important;
|
||||
border-width: 2px !important;
|
||||
}
|
||||
|
||||
@@ -383,8 +383,8 @@ header {
|
||||
}
|
||||
|
||||
.btn-icon.btn-icon-primary:hover {
|
||||
background-color: $light-300 !important;
|
||||
color: $primary-500 !important
|
||||
background-color: var(--pgn-color-light-300) !important;
|
||||
color: var(--pgn-color-primary-500) !important
|
||||
}
|
||||
|
||||
|
||||
@@ -427,38 +427,38 @@ header {
|
||||
}
|
||||
|
||||
.hover-button:hover {
|
||||
background-color: $light-300 !important;
|
||||
background-color: var(--pgn-color-light-300) !important;
|
||||
height: 36px !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.btn-tertiary:hover {
|
||||
background-color: $light-300 !important;
|
||||
background-color: var(--pgn-color-light-300) !important;
|
||||
}
|
||||
|
||||
.nav-button-group {
|
||||
.nav-link {
|
||||
&:hover {
|
||||
background-color: $light-300 !important;
|
||||
background-color: var(--pgn-color-light-300) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-link.active,
|
||||
.show>.nav-link {
|
||||
background-color: $primary-500 !important;
|
||||
background-color: var(--pgn-color-primary-500) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.course-tabs-navigation {
|
||||
.nav a {
|
||||
&:hover {
|
||||
background-color: $light-300 !important;;
|
||||
background-color: var(--pgn-color-light-300) !important;;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-tertiary:disabled {
|
||||
color: $gray-700 !important;
|
||||
color: var(--pgn-color-gray-700) !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
@@ -535,14 +535,14 @@ code {
|
||||
.post-preview,
|
||||
.discussion-comments {
|
||||
blockquote {
|
||||
border-left: 2px solid $gray-200;
|
||||
border-left: 2px solid var(--pgn-color-gray-200);
|
||||
margin-left: 1.5rem;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.add-comment-btn {
|
||||
border: 1px solid $light-300 !important;
|
||||
border: 1px solid var(--pgn-color-light-300) !important;
|
||||
}
|
||||
|
||||
.icon-size-24 {
|
||||
@@ -588,7 +588,7 @@ code {
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px dashed $gray-200;
|
||||
border: 1px dashed var(--pgn-color-gray-200);
|
||||
padding: 0.4rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# Footer Slot
|
||||
|
||||
### Slot ID: `footer_slot`
|
||||
### Slot ID: `org.openedx.frontend.layout.footer.v1`
|
||||
|
||||
## Description
|
||||
### Slot ID Aliases
|
||||
* `footer_slot`
|
||||
|
||||
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 +24,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-discussions` Plugin Slots
|
||||
|
||||
* [`footer_slot`](./FooterSlot/)
|
||||
* [`org.openedx.frontend.layout.footer.v1`](./FooterSlot/)
|
||||
|
||||
Reference in New Issue
Block a user