Merge branch 'master' into ahtisham/INF-780
This commit is contained in:
@@ -13,41 +13,178 @@
|
||||
window.MathJax = {
|
||||
tex: {
|
||||
inlineMath: [
|
||||
['$', '$'],
|
||||
['\\\\(', '\\\\)'],
|
||||
['\\(', '\\)'],
|
||||
['[mathjaxinline]', '[/mathjaxinline]'],
|
||||
['\\begin{math}', '\\end{math}'],
|
||||
["$", "$"],
|
||||
["\\\\(", "\\\\)"],
|
||||
["\\(", "\\)"],
|
||||
["[mathjaxinline]", "[/mathjaxinline]"],
|
||||
["\\begin{math}", "\\end{math}"],
|
||||
],
|
||||
displayMath: [
|
||||
['[mathjax]', '[/mathjax]'],
|
||||
['$$', '$$'],
|
||||
['\\\\[', '\\\\]'],
|
||||
['\\[', '\\]'],
|
||||
['\\begin{displaymath}', '\\end{displaymath}'],
|
||||
['\\begin{equation}', '\\end{equation}'],
|
||||
["[mathjax]", "[/mathjax]"],
|
||||
["$$", "$$"],
|
||||
["\\\\[", "\\\\]"],
|
||||
["\\[", "\\]"],
|
||||
["\\begin{displaymath}", "\\end{displaymath}"],
|
||||
["\\begin{equation}", "\\end{equation}"],
|
||||
],
|
||||
processEscapes: true,
|
||||
processEnvironments: true,
|
||||
autoload: {
|
||||
color: [],
|
||||
colorv2: ['color']
|
||||
colorv2: ["color"],
|
||||
},
|
||||
packages: {'[+]': ['noerrors']}
|
||||
packages: { "[+]": ["noerrors"] },
|
||||
},
|
||||
options: {
|
||||
ignoreHtmlClass: 'tex2jax_ignore',
|
||||
processHtmlClass: 'tex2jax_process'
|
||||
ignoreHtmlClass: "tex2jax_ignore",
|
||||
processHtmlClass: "tex2jax_process",
|
||||
},
|
||||
loader: {
|
||||
load: ['input/asciimath', '[tex]/noerrors']
|
||||
}
|
||||
load: ["input/asciimath", "[tex]/noerrors"],
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<script async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" id="MathJax-script"></script>
|
||||
|
||||
<script
|
||||
async
|
||||
src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"
|
||||
id="MathJax-script"
|
||||
></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" class="small"></div>
|
||||
|
||||
<!-- begin usabilla live embed code -->
|
||||
<script type="text/javascript">
|
||||
window.lightningjs ||
|
||||
(function (n) {
|
||||
var e = "lightningjs";
|
||||
function t(e, t) {
|
||||
var r, i, a, o, d, c;
|
||||
return (
|
||||
t && (t += (/\?/.test(t) ? "&" : "?") + "lv=1"),
|
||||
n[e] ||
|
||||
((r = window),
|
||||
(i = document),
|
||||
(a = e),
|
||||
(o = i.location.protocol),
|
||||
(d = "load"),
|
||||
(c = 0),
|
||||
(function () {
|
||||
n[a] = function () {
|
||||
var t = arguments,
|
||||
i = this,
|
||||
o = ++c,
|
||||
d = (i && i != r && i.id) || 0;
|
||||
function s() {
|
||||
return (s.id = o), n[a].apply(s, arguments);
|
||||
}
|
||||
return (
|
||||
(e.s = e.s || []).push([o, d, t]),
|
||||
(s.then = function (n, t, r) {
|
||||
var i = (e.fh[o] = e.fh[o] || []),
|
||||
a = (e.eh[o] = e.eh[o] || []),
|
||||
d = (e.ph[o] = e.ph[o] || []);
|
||||
return (
|
||||
n && i.push(n), t && a.push(t), r && d.push(r), s
|
||||
);
|
||||
}),
|
||||
s
|
||||
);
|
||||
};
|
||||
var e = (n[a]._ = {});
|
||||
function s() {
|
||||
e.P(d), (e.w = 1), n[a]("_load");
|
||||
}
|
||||
(e.fh = {}),
|
||||
(e.eh = {}),
|
||||
(e.ph = {}),
|
||||
(e.l = t
|
||||
? t.replace(/^\/\//, ("https:" == o ? o : "http:") + "//")
|
||||
: t),
|
||||
(e.p = { 0: +new Date() }),
|
||||
(e.P = function (n) {
|
||||
e.p[n] = new Date() - e.p[0];
|
||||
}),
|
||||
e.w && s(),
|
||||
r.addEventListener
|
||||
? r.addEventListener(d, s, !1)
|
||||
: r.attachEvent("onload", s);
|
||||
var l = function () {
|
||||
function n() {
|
||||
return [
|
||||
"<!DOCTYPE ",
|
||||
o,
|
||||
"><",
|
||||
o,
|
||||
"><head></head><",
|
||||
t,
|
||||
"><",
|
||||
r,
|
||||
' src="',
|
||||
e.l,
|
||||
'"></',
|
||||
r,
|
||||
"></",
|
||||
t,
|
||||
"></",
|
||||
o,
|
||||
">",
|
||||
].join("");
|
||||
}
|
||||
var t = "body",
|
||||
r = "script",
|
||||
o = "html",
|
||||
d = i[t];
|
||||
if (!d) return setTimeout(l, 100);
|
||||
e.P(1);
|
||||
var c,
|
||||
s = i.createElement("div"),
|
||||
h = s.appendChild(i.createElement("div")),
|
||||
u = i.createElement("iframe");
|
||||
(s.style.display = "none"),
|
||||
(d.insertBefore(s, d.firstChild).id = "lightningjs-" + a),
|
||||
(u.frameBorder = "0"),
|
||||
(u.id = "lightningjs-frame-" + a),
|
||||
/MSIE[ ]+6/.test(navigator.userAgent) &&
|
||||
(u.src = "javascript:false"),
|
||||
(u.allowTransparency = "true"),
|
||||
h.appendChild(u);
|
||||
try {
|
||||
u.contentWindow.document.open();
|
||||
} catch (n) {
|
||||
(e.domain = i.domain),
|
||||
(c =
|
||||
"javascript:var d=document.open();d.domain='" +
|
||||
i.domain +
|
||||
"';"),
|
||||
(u.src = c + "void(0);");
|
||||
}
|
||||
try {
|
||||
var p = u.contentWindow.document;
|
||||
p.write(n()), p.close();
|
||||
} catch (e) {
|
||||
u.src =
|
||||
c +
|
||||
'd.write("' +
|
||||
n().replace(/"/g, String.fromCharCode(92) + '"') +
|
||||
'");d.close();';
|
||||
}
|
||||
e.P(2);
|
||||
};
|
||||
e.l && l();
|
||||
})()),
|
||||
(n[e].lv = "1"),
|
||||
n[e]
|
||||
);
|
||||
}
|
||||
var r = (window.lightningjs = t(e));
|
||||
(r.require = t), (r.modules = n);
|
||||
})({});
|
||||
window.usabilla_live = lightningjs.require(
|
||||
"usabilla_live",
|
||||
"//w.usabilla.com/9e6036348fa1.js"
|
||||
);
|
||||
</script>
|
||||
<!-- end usabilla live embed code -->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -13,12 +13,12 @@ const defaultSanitizeOptions = {
|
||||
};
|
||||
|
||||
function HTMLLoader({
|
||||
htmlNode, componentId, cssClassName, testId,
|
||||
htmlNode, componentId, cssClassName, testId, delay,
|
||||
}) {
|
||||
const sanitizedMath = DOMPurify.sanitize(htmlNode, { ...defaultSanitizeOptions });
|
||||
const previewRef = useRef();
|
||||
|
||||
const debouncedPostContent = useDebounce(htmlNode, 500);
|
||||
const debouncedPostContent = useDebounce(htmlNode, delay);
|
||||
|
||||
useEffect(() => {
|
||||
let promise = Promise.resolve(); // Used to hold chain of typesetting calls
|
||||
@@ -45,6 +45,7 @@ HTMLLoader.propTypes = {
|
||||
componentId: PropTypes.string,
|
||||
cssClassName: PropTypes.string,
|
||||
testId: PropTypes.string,
|
||||
delay: PropTypes.number,
|
||||
};
|
||||
|
||||
HTMLLoader.defaultProps = {
|
||||
@@ -52,6 +53,7 @@ HTMLLoader.defaultProps = {
|
||||
componentId: null,
|
||||
cssClassName: '',
|
||||
testId: '',
|
||||
delay: 0,
|
||||
};
|
||||
|
||||
export default HTMLLoader;
|
||||
|
||||
@@ -29,7 +29,13 @@ function PostPreviewPane({
|
||||
className="float-right p-3"
|
||||
iconClassNames="icon-size"
|
||||
/>
|
||||
<HTMLLoader htmlNode={htmlNode} cssClassName="text-primary" componentId="post-preview" testId="post-preview" />
|
||||
<HTMLLoader
|
||||
htmlNode={htmlNode}
|
||||
cssClassName="text-primary"
|
||||
componentId="post-preview"
|
||||
testId="post-preview"
|
||||
delay={500}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="d-flex justify-content-end">
|
||||
|
||||
233
src/discussions/in-context-topics/TopicsView.test.jsx
Normal file
233
src/discussions/in-context-topics/TopicsView.test.jsx
Normal file
@@ -0,0 +1,233 @@
|
||||
import {
|
||||
fireEvent, render, screen, waitFor,
|
||||
within,
|
||||
} from '@testing-library/react';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { MemoryRouter, Route } from 'react-router';
|
||||
import { Factory } from 'rosie';
|
||||
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
|
||||
import { initializeStore } from '../../store';
|
||||
import { executeThunk } from '../../test-utils';
|
||||
import { DiscussionContext } from '../common/context';
|
||||
import { getCourseTopicsApiUrl } from './data/api';
|
||||
import { selectCoursewareTopics, selectNonCoursewareTopics } from './data/selectors';
|
||||
import { fetchCourseTopicsV3 } from './data/thunks';
|
||||
import TopicPostsView from './TopicPostsView';
|
||||
import TopicsView from './TopicsView';
|
||||
|
||||
import './data/__factories__';
|
||||
|
||||
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
||||
const category = 'section-topic-1';
|
||||
|
||||
const topicsApiUrl = `${getCourseTopicsApiUrl()}`;
|
||||
let store;
|
||||
let axiosMock;
|
||||
let lastLocation;
|
||||
let container;
|
||||
|
||||
function renderComponent() {
|
||||
const wrapper = render(
|
||||
<IntlProvider locale="en">
|
||||
<AppProvider store={store}>
|
||||
<DiscussionContext.Provider value={{ courseId, category }}>
|
||||
<MemoryRouter initialEntries={[`/${courseId}/topics/`]}>
|
||||
<Route path="/:courseId/topics/">
|
||||
<TopicsView />
|
||||
</Route>
|
||||
<Route path="/:courseId/category/:category">
|
||||
<TopicPostsView />
|
||||
</Route>
|
||||
<Route
|
||||
render={({ location }) => {
|
||||
lastLocation = location;
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</DiscussionContext.Provider>
|
||||
</AppProvider>
|
||||
</IntlProvider>,
|
||||
);
|
||||
container = wrapper.container;
|
||||
}
|
||||
|
||||
describe('InContext Topics View', () => {
|
||||
let nonCoursewareTopics;
|
||||
let coursewareTopics;
|
||||
beforeEach(() => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
|
||||
store = initializeStore({
|
||||
config: { enableInContext: true, provider: 'openedx', hasModerationPrivileges: true },
|
||||
});
|
||||
Factory.resetAll();
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
lastLocation = undefined;
|
||||
});
|
||||
|
||||
async function setupMockResponse() {
|
||||
axiosMock.onGet(`${topicsApiUrl}${courseId}`)
|
||||
.reply(200, (Factory.buildList('topic', 1, null, {
|
||||
topicPrefix: 'noncourseware-topic',
|
||||
enabledInContext: true,
|
||||
topicNamePrefix: 'general-topic',
|
||||
usageKey: '',
|
||||
courseware: false,
|
||||
discussionCount: 1,
|
||||
questionCount: 1,
|
||||
}).concat(Factory.buildList('section', 2, null, { topicPrefix: 'courseware' })))
|
||||
.concat(Factory.buildList('archived-topics', 2, null)));
|
||||
await executeThunk(fetchCourseTopicsV3(courseId), store.dispatch, store.getState);
|
||||
|
||||
const state = store.getState();
|
||||
nonCoursewareTopics = selectNonCoursewareTopics(state);
|
||||
coursewareTopics = selectCoursewareTopics(state);
|
||||
}
|
||||
|
||||
it('A non-courseware topic should be clickable and should have a title', async () => {
|
||||
await setupMockResponse();
|
||||
renderComponent();
|
||||
|
||||
const nonCourseware = nonCoursewareTopics[0];
|
||||
const nonCoursewareTopic = await screen.findByText(nonCourseware.name);
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(nonCoursewareTopic);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(nonCourseware.name)).toBeInTheDocument();
|
||||
expect(lastLocation.pathname.endsWith(`/topics/${nonCourseware.id}`)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('A non-courseware topic should be on the top of the list', async () => {
|
||||
await setupMockResponse();
|
||||
renderComponent();
|
||||
const topic = await container.querySelector('.discussion-topic');
|
||||
|
||||
expect(within(topic).queryByText('general-topic-1')).toBeInTheDocument();
|
||||
expect(topic.nextSibling).toBe(container.querySelector('.divider'));
|
||||
});
|
||||
|
||||
it('A non-Courseware topic should have 3 stats and should be hoverable', async () => {
|
||||
await setupMockResponse();
|
||||
renderComponent();
|
||||
|
||||
const topic = await container.querySelector('.discussion-topic');
|
||||
const statsList = await topic.querySelectorAll('.icon-size');
|
||||
|
||||
expect(statsList.length).toBe(3);
|
||||
fireEvent.mouseOver(statsList[0]);
|
||||
expect(screen.queryByText('1 Discussion')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Section groups should be listed in the middle of the topics list.', async () => {
|
||||
await setupMockResponse();
|
||||
renderComponent();
|
||||
const topicsList = await screen.getByRole('list');
|
||||
const sectionGroups = await screen.getAllByTestId('section-group');
|
||||
|
||||
expect(topicsList.children[1]).toStrictEqual(topicsList.querySelector('.divider'));
|
||||
expect(sectionGroups.length).toBe(2);
|
||||
expect(topicsList.children[5]).toStrictEqual(topicsList.querySelector('.divider'));
|
||||
});
|
||||
|
||||
it('A section group should have only a title and required subsections.', async () => {
|
||||
await setupMockResponse();
|
||||
renderComponent();
|
||||
const sectionGroups = await screen.getAllByTestId('section-group');
|
||||
|
||||
coursewareTopics.forEach(async (topic, index) => {
|
||||
const stats = await sectionGroups[index].querySelectorAll('.icon-size:not([data-testid="subsection-group"].icon-size)');
|
||||
const subsectionGroups = await within(sectionGroups[index]).getAllByTestId('subsection-group');
|
||||
|
||||
expect(within(sectionGroups[index]).queryByText(topic.displayName)).toBeInTheDocument();
|
||||
expect(stats).toHaveLength(0);
|
||||
expect(subsectionGroups).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('The subsection should have a title name, be clickable, and have the stats', async () => {
|
||||
await setupMockResponse();
|
||||
renderComponent();
|
||||
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 statsList = await subSection.querySelectorAll('.icon-size');
|
||||
|
||||
expect(subSectionTitle).toBeInTheDocument();
|
||||
expect(statsList).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('Subsection names should be clickable and redirected to the units lists', async () => {
|
||||
await setupMockResponse();
|
||||
renderComponent();
|
||||
|
||||
const subsectionObject = coursewareTopics[0].children[0];
|
||||
const subSection = await container.querySelector(`[data-subsection-id=${subsectionObject.id}]`);
|
||||
|
||||
await act(async () => fireEvent.click(subSection));
|
||||
await waitFor(async () => {
|
||||
const backButton = await screen.getByLabelText('Back to topics list');
|
||||
const topicsList = await screen.getByRole('list');
|
||||
const subSectionHeading = await screen.findByText(subsectionObject.displayName);
|
||||
const units = await topicsList.querySelectorAll('.discussion-topic');
|
||||
|
||||
expect(backButton).toBeInTheDocument();
|
||||
expect(subSectionHeading).toBeInTheDocument();
|
||||
expect(units).toHaveLength(4);
|
||||
expect(lastLocation.pathname.endsWith(`/category/${subsectionObject.id}`)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('The number of units should be matched with the actual unit length.', async () => {
|
||||
await setupMockResponse();
|
||||
renderComponent();
|
||||
const subSection = await container.querySelector(`[data-subsection-id=${coursewareTopics[0].children[0].id}]`);
|
||||
|
||||
await act(async () => fireEvent.click(subSection));
|
||||
await waitFor(async () => {
|
||||
const units = await container.querySelectorAll('.discussion-topic');
|
||||
|
||||
expect(units).toHaveLength(4);
|
||||
});
|
||||
});
|
||||
|
||||
it('A unit should have a title and stats and should be clickable', async () => {
|
||||
await setupMockResponse();
|
||||
renderComponent();
|
||||
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 waitFor(async () => {
|
||||
const unitElement = await screen.findByText(unitObject.name);
|
||||
const unitContainer = await container.querySelector(`[data-topic-id=${unitObject.id}]`);
|
||||
const statsList = await unitContainer.querySelectorAll('.icon-size');
|
||||
|
||||
expect(unitElement).toBeInTheDocument();
|
||||
expect(statsList).toHaveLength(3);
|
||||
|
||||
await act(async () => fireEvent.click(unitContainer));
|
||||
await waitFor(async () => {
|
||||
expect(lastLocation.pathname.endsWith(`/topics/${unitObject.id}`)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,7 @@ Factory.define('topic')
|
||||
.sequence('name', ['topicNamePrefix'], (idx, topicNamePrefix) => `${topicNamePrefix}-${idx}`)
|
||||
.sequence('usage-key', ['usageKey'], (idx, usageKey) => usageKey)
|
||||
.sequence('courseware', ['courseware'], (idx, courseware) => courseware)
|
||||
|
||||
.attr('activeFlags', null, true)
|
||||
.attr('thread_counts', ['discussionCount', 'questionCount'], (discCount, questCount) => {
|
||||
Factory.reset('thread-counts');
|
||||
return Factory.build('thread-counts', null, { discussionCount: discCount, questionCount: questCount });
|
||||
@@ -27,6 +27,11 @@ Factory.define('sub-section')
|
||||
.sequence('student_view_url', ['id', 'courseId'],
|
||||
(idx, id) => `${getApiBaseUrl}/xblock/block-v1:${id}`)
|
||||
.attr('type', null, 'sequential')
|
||||
.attr('activeFlags', null, true)
|
||||
.attr('thread_counts', ['discussionCount', 'questionCount'], (discCount, questCount) => {
|
||||
Factory.reset('thread-counts');
|
||||
return Factory.build('thread-counts', null, { discussionCount: discCount, questionCount: questCount });
|
||||
})
|
||||
.attr('children', ['id', 'display-name', 'courseId'], (id, name, courseId) => {
|
||||
Factory.reset('topic');
|
||||
return Factory.buildList('topic', 2, null, {
|
||||
@@ -42,7 +47,7 @@ Factory.define('sub-section')
|
||||
Factory.define('section')
|
||||
.sequence('block_id', (idx) => `${idx}`)
|
||||
.option('topicPrefix', null, '')
|
||||
.sequence('id', ['topicPrefix'], (idx, topicPrefix) => `${topicPrefix}-topic-${idx}`)
|
||||
.sequence('id', ['topicPrefix'], (idx, topicPrefix) => `${topicPrefix}-topic-${idx}-v3`)
|
||||
.attr('courseware', null, true)
|
||||
.sequence('display-name', (idx) => `Introduction ${idx}`)
|
||||
.option('courseId', null, 'course-v1:edX+DemoX+Demo_Course')
|
||||
@@ -53,9 +58,15 @@ Factory.define('section')
|
||||
.sequence('student_view_url', ['id', 'courseId'],
|
||||
(idx, id, courseId) => `${getApiBaseUrl}/xblock/${courseId.replace('course-v1:', 'block-v1:')}+type@chapter+block@${id}`)
|
||||
.attr('type', null, 'chapter')
|
||||
.attr('children', ['display-name'], (name) => {
|
||||
.attr('children', ['id', 'display-name'], (id, name) => {
|
||||
Factory.reset('sub-section');
|
||||
return Factory.buildList('sub-section', 2, null, { sectionPrefix: `${name}-`, topicPrefix: 'section' });
|
||||
return Factory.buildList('sub-section', 2, null, {
|
||||
sectionPrefix: `${name}-`,
|
||||
topicPrefix: 'section',
|
||||
id,
|
||||
discussionCount: 1,
|
||||
questionCount: 1,
|
||||
});
|
||||
});
|
||||
|
||||
Factory.define('thread-counts')
|
||||
|
||||
@@ -101,7 +101,7 @@ describe('Redux in context topics tests', () => {
|
||||
// contain chapter at first level
|
||||
coursewareTopics.forEach((chapter, index) => {
|
||||
expect(chapter.courseware).toEqual(true);
|
||||
expect(chapter.id).toEqual(`courseware-topic-${index + 1}`);
|
||||
expect(chapter.id).toEqual(`courseware-topic-${index + 1}-v3`);
|
||||
expect(chapter.type).toEqual('chapter');
|
||||
expect(chapter).toHaveProperty('blockId');
|
||||
expect(chapter).toHaveProperty('lmsWebUrl');
|
||||
@@ -120,7 +120,7 @@ describe('Redux in context topics tests', () => {
|
||||
// contain sub section at third level
|
||||
section.children.forEach((subSection, subSecIndex) => {
|
||||
expect(subSection.enabledInContext).toEqual(true);
|
||||
expect(subSection.id).toEqual(`${section.id}-${subSecIndex + 1}`);
|
||||
expect(subSection.id).toEqual(`courseware-topic-${index + 1}-v3-${subSecIndex + 1}`);
|
||||
expect(subSection).toHaveProperty('usageKey');
|
||||
expect(subSection).not.toHaveProperty('blockId');
|
||||
expect(subSection?.threadCounts?.discussion).toEqual(1);
|
||||
|
||||
@@ -88,7 +88,7 @@ describe('In Context Topics Selector test cases', () => {
|
||||
|
||||
expect(coursewareTopics).not.toBeUndefined();
|
||||
coursewareTopics.forEach((topic, index) => {
|
||||
expect(topic?.id).toEqual(`courseware-topic-${index + 1}`);
|
||||
expect(topic?.id).toEqual(`courseware-topic-${index + 1}-v3`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -50,7 +50,7 @@ function SectionBaseGroup({
|
||||
aria-current={isSelected(section.id) ? 'page' : undefined}
|
||||
tabIndex={(isSelected(subsection.id) || index === 0) ? 0 : -1}
|
||||
>
|
||||
<div className="d-flex flex-row py-3.5 px-4">
|
||||
<div className="d-flex flex-row pt-2.5 pb-2 px-4">
|
||||
<div className="d-flex flex-column flex-fill" style={{ minWidth: 0 }}>
|
||||
<div className="d-flex flex-column justify-content-start mw-100 flex-fill">
|
||||
<div className="topic-name text-truncate">
|
||||
|
||||
@@ -40,6 +40,7 @@ function PostCommentsView({ intl }) {
|
||||
const {
|
||||
courseId, learnerUsername, category, topicId, page, enableInContextSidebar,
|
||||
} = useContext(DiscussionContext);
|
||||
const enableCommentsSort = false;
|
||||
|
||||
useEffect(() => {
|
||||
if (!thread) { submitDispatch(fetchThread(postId, courseId, true)); }
|
||||
@@ -109,7 +110,7 @@ function PostCommentsView({ intl }) {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{!!commentsCount && commentsStatus === RequestStatus.SUCCESSFUL && <CommentsSort />}
|
||||
{!!commentsCount && commentsStatus === RequestStatus.SUCCESSFUL && enableCommentsSort && <CommentsSort />}
|
||||
{thread.type === ThreadType.DISCUSSION && (
|
||||
<CommentsView
|
||||
postId={postId}
|
||||
|
||||
@@ -19,7 +19,6 @@ import DiscussionContent from '../discussions-home/DiscussionContent';
|
||||
import { getThreadsApiUrl } from '../posts/data/api';
|
||||
import { fetchThreads } from '../posts/data/thunks';
|
||||
import { getCommentsApiUrl } from './data/api';
|
||||
import { removeComment } from './data/thunks';
|
||||
|
||||
import '../posts/data/__factories__';
|
||||
import './data/__factories__';
|
||||
@@ -35,7 +34,6 @@ const reverseOrder = false;
|
||||
let store;
|
||||
let axiosMock;
|
||||
let testLocation;
|
||||
let container;
|
||||
|
||||
function mockAxiosReturnPagedComments() {
|
||||
[null, false, true].forEach(endorsed => {
|
||||
@@ -106,7 +104,7 @@ function renderComponent(postId) {
|
||||
</AppProvider>
|
||||
</IntlProvider>,
|
||||
);
|
||||
container = wrapper.container;
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
describe('ThreadView', () => {
|
||||
@@ -698,68 +696,4 @@ describe('ThreadView', () => {
|
||||
expect(screen.queryByRole('dialog', { name: /Delete/i, exact: false })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('for comments sort', () => {
|
||||
it('should show sort dropdown if there are endorse or unendorsed comments', async () => {
|
||||
renderComponent(discussionPostId);
|
||||
|
||||
const comment = await waitFor(() => screen.findByTestId('comment-comment-1'));
|
||||
const sortWrapper = container.querySelector('.comments-sort');
|
||||
const sortDropDown = within(sortWrapper).getByRole('button', { name: /Oldest first/i });
|
||||
|
||||
expect(comment).toBeInTheDocument();
|
||||
expect(sortDropDown).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show sort dropdown if there is no response', async () => {
|
||||
const commentId = 'comment-1';
|
||||
renderComponent(discussionPostId);
|
||||
|
||||
await waitFor(() => screen.findByTestId('comment-comment-1'));
|
||||
axiosMock.onDelete(`${commentsApiUrl}${commentId}/`).reply(201);
|
||||
await executeThunk(removeComment(commentId, discussionPostId), store.dispatch, store.getState);
|
||||
|
||||
expect(await waitFor(() => screen.findByText('No responses', { exact: true }))).toBeInTheDocument();
|
||||
expect(container.querySelector('.comments-sort')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have only two options', async () => {
|
||||
renderComponent(discussionPostId);
|
||||
|
||||
await waitFor(() => screen.findByTestId('comment-comment-1'));
|
||||
await act(async () => { fireEvent.click(screen.getByRole('button', { name: /Oldest first/i })); });
|
||||
const dropdown = await waitFor(() => screen.findByTestId('comment-sort-dropdown-modal-popup'));
|
||||
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
expect(await within(dropdown).getAllByRole('button')).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should be selected Oldest first and auto focus', async () => {
|
||||
renderComponent(discussionPostId);
|
||||
|
||||
await waitFor(() => screen.findByTestId('comment-comment-1'));
|
||||
await act(async () => { fireEvent.click(screen.getByRole('button', { name: /Oldest first/i })); });
|
||||
const dropdown = await waitFor(() => screen.findByTestId('comment-sort-dropdown-modal-popup'));
|
||||
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
expect(within(dropdown).getByRole('button', { name: /Oldest first/i })).toBeInTheDocument();
|
||||
expect(within(dropdown).getByRole('button', { name: /Oldest first/i })).toHaveFocus();
|
||||
expect(within(dropdown).getByRole('button', { name: /Newest first/i })).not.toHaveFocus();
|
||||
});
|
||||
|
||||
test('successfully handles sort state update', async () => {
|
||||
renderComponent(discussionPostId);
|
||||
|
||||
expect(store.getState().comments.sortOrder).toBeFalsy();
|
||||
|
||||
await waitFor(() => screen.findByTestId('comment-comment-1'));
|
||||
await act(async () => { fireEvent.click(screen.getByRole('button', { name: /Oldest first/i })); });
|
||||
const dropdown = await waitFor(() => screen.findByTestId('comment-sort-dropdown-modal-popup'));
|
||||
await act(async () => {
|
||||
fireEvent.click(within(dropdown).getByRole('button', { name: /Newest first/i }));
|
||||
});
|
||||
|
||||
expect(store.getState().comments.sortOrder).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user