fix: fixed email confirmation params issue (#791)

* fix: fixed email confirmation params issue
This commit is contained in:
sundasnoreen12
2025-07-23 20:32:11 +05:00
committed by GitHub
parent 76da74ae20
commit fa772053c4
13 changed files with 119 additions and 39 deletions

View File

@@ -5,7 +5,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { useIntl } from '@edx/frontend-platform/i18n';
import { RequestStatus } from '../../data/constants';
import { selectConfirmEmailStatus, selectOnlyVerifiedUsersCanPost } from '../data/selectors';
import { selectConfirmEmailStatus, selectShouldShowEmailConfirmation } from '../data/selectors';
import { sendAccountActivationEmail } from '../posts/data/thunks';
import postMessages from '../posts/post-actions-bar/messages';
import { Confirmation } from '.';
@@ -15,7 +15,7 @@ const withEmailConfirmation = (WrappedComponent) => {
const intl = useIntl();
const dispatch = useDispatch();
const [isConfirming, setIsConfirming] = useState(false);
const onlyVerifiedUsersCanPost = useSelector(selectOnlyVerifiedUsersCanPost);
const shouldShowEmailConfirmation = useSelector(selectShouldShowEmailConfirmation);
const confirmEmailStatus = useSelector(selectConfirmEmailStatus);
const openConfirmation = useCallback(() => {
@@ -42,7 +42,7 @@ const withEmailConfirmation = (WrappedComponent) => {
{...props}
openEmailConfirmation={openConfirmation}
/>
{!onlyVerifiedUsersCanPost
{shouldShowEmailConfirmation
&& (
<Confirmation
isOpen={isConfirming}

View File

@@ -44,7 +44,9 @@ describe('EmptyPage', () => {
},
});
store = initializeStore();
store = initializeStore({
config: { provider: 'openedx', onlyVerifiedUsersCanPost: true },
});
});
it('should open the confirmation link dialogue box.', async () => {

View File

@@ -43,6 +43,11 @@ export const selectOnlyVerifiedUsersCanPost = state => state.config.onlyVerified
export const selectConfirmEmailStatus = state => state.threads.confirmEmailStatus;
export const selectShouldShowEmailConfirmation = createSelector(
[selectIsEmailVerified, selectOnlyVerifiedUsersCanPost],
(isEmailVerified, onlyVerifiedUsersCanPost) => !isEmailVerified && onlyVerifiedUsersCanPost,
);
export const selectModerationSettings = state => ({
postCloseReasons: state.config.postCloseReasons,
editReasons: state.config.editReasons,

View File

@@ -9,8 +9,8 @@ import withEmailConfirmation from '../common/withEmailConfirmation';
import { useIsOnTablet } from '../data/hooks';
import {
selectAreThreadsFiltered,
selectIsEmailVerified,
selectPostThreadCount,
selectShouldShowEmailConfirmation,
} from '../data/selectors';
import messages from '../messages';
import { showPostEditor } from '../posts/data';
@@ -23,11 +23,11 @@ const EmptyPosts = ({ subTitleMessage, openEmailConfirmation }) => {
const isOnTabletorDesktop = useIsOnTablet();
const isFiltered = useSelector(selectAreThreadsFiltered);
const totalThreads = useSelector(selectPostThreadCount);
const isEmailVerified = useSelector(selectIsEmailVerified);
const shouldShowEmailConfirmation = useSelector(selectShouldShowEmailConfirmation);
const addPost = useCallback(() => {
if (isEmailVerified) { dispatch(showPostEditor()); } else { openEmailConfirmation(); }
}, [isEmailVerified, openEmailConfirmation]);
if (shouldShowEmailConfirmation) { openEmailConfirmation(); } else { dispatch(showPostEditor()); }
}, [shouldShowEmailConfirmation, openEmailConfirmation]);
let title = messages.noPostSelected;
let subTitle = null;

View File

@@ -8,9 +8,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import withEmailConfirmation from '../common/withEmailConfirmation';
import { useIsOnTablet, useTotalTopicThreadCount } from '../data/hooks';
import {
selectIsEmailVerified, selectTopicThreadCount,
} from '../data/selectors';
import { selectShouldShowEmailConfirmation, selectTopicThreadCount } from '../data/selectors';
import messages from '../messages';
import { showPostEditor } from '../posts/data';
import postMessages from '../posts/post-actions-bar/messages';
@@ -23,11 +21,11 @@ const EmptyTopics = ({ openEmailConfirmation }) => {
const isOnTabletorDesktop = useIsOnTablet();
const hasGlobalThreads = useTotalTopicThreadCount() > 0;
const topicThreadCount = useSelector(selectTopicThreadCount(topicId));
const isEmailVerified = useSelector(selectIsEmailVerified);
const shouldShowEmailConfirmation = useSelector(selectShouldShowEmailConfirmation);
const addPost = useCallback(() => {
if (isEmailVerified) { dispatch(showPostEditor()); } else { openEmailConfirmation(); }
}, [isEmailVerified, openEmailConfirmation]);
if (shouldShowEmailConfirmation) { openEmailConfirmation(); } else { dispatch(showPostEditor()); }
}, [shouldShowEmailConfirmation, openEmailConfirmation]);
let title = messages.emptyTitle;
let fullWidth = false;

View File

@@ -1,4 +1,5 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import MockAdapter from 'axios-mock-adapter';
import { IntlProvider } from 'react-intl';
import { Context as ResponsiveContext } from 'react-responsive';
@@ -61,7 +62,7 @@ describe('EmptyTopics', () => {
},
});
store = initializeStore({ config: { provider: 'legacy' } });
store = initializeStore({ config: { provider: 'legacy', onlyVerifiedUsersCanPost: true } });
});
test('"no topic selected" text shown when viewing topics page', async () => {
@@ -75,4 +76,13 @@ describe('EmptyTopics', () => {
await screen.findByText(messages.noPostSelected.defaultMessage);
});
it('should open the confirmation link dialogue box.', async () => {
renderComponent(`/${courseId}/topics/ncwtopic-3/`);
const addPostButton = screen.getByRole('button', { name: 'Add a post' });
await userEvent.click(addPostButton);
expect(screen.queryByText('Send confirmation link')).toBeInTheDocument();
});
});

View File

@@ -1,3 +1,4 @@
import { ResponsiveContext } from '@openedx/paragon';
import {
fireEvent, render, screen, waitFor,
within,
@@ -18,6 +19,7 @@ import { AppProvider } from '@edx/frontend-platform/react';
import { initializeStore } from '../../store';
import executeThunk from '../../test-utils';
import DiscussionContext from '../common/context';
import EmptyTopics from './components/EmptyTopics';
import { getCourseTopicsApiUrl } from './data/api';
import { selectCoursewareTopics, selectNonCoursewareTopics } from './data/selectors';
import fetchCourseTopicsV3 from './data/thunks';
@@ -40,6 +42,24 @@ const LocationComponent = () => {
return null;
};
function renderEmptyTopicComponent(topicId = 'sample-topic-id') {
return render(
<IntlProvider locale="en">
<ResponsiveContext.Provider value={{ width: 1280 }}>
<AppProvider store={store} wrapWithRouter={false}>
<DiscussionContext.Provider value={{ courseId, category }}>
<MemoryRouter initialEntries={[`/discussion/${category}/${topicId}`]}>
<Routes>
<Route path="/discussion/:category/:topicId" element={<EmptyTopics />} />
</Routes>
</MemoryRouter>
</DiscussionContext.Provider>
</AppProvider>
</ResponsiveContext.Provider>
</IntlProvider>,
);
}
function renderComponent() {
const wrapper = render(
<IntlProvider locale="en">
@@ -72,7 +92,9 @@ describe('InContext Topics View', () => {
});
store = initializeStore({
config: { enableInContext: true, provider: 'openedx', hasModerationPrivileges: true },
config: {
enableInContext: true, provider: 'openedx', hasModerationPrivileges: true, onlyVerifiedUsersCanPost: true,
},
});
Factory.resetAll();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
@@ -238,4 +260,14 @@ describe('InContext Topics View', () => {
});
});
});
it('should open the confirmation link dialogue box.', async () => {
renderEmptyTopicComponent();
const addPostButton = screen.getByRole('button', { name: /Add a post/i });
await userEvent.click(addPostButton);
const confirmationText = await screen.findByText(/send confirmation link/i);
expect(confirmationText).toBeInTheDocument();
});
});

View File

@@ -9,7 +9,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import DiscussionContext from '../../common/context';
import withEmailConfirmation from '../../common/withEmailConfirmation';
import { useIsOnTablet } from '../../data/hooks';
import { selectIsEmailVerified, selectPostThreadCount } from '../../data/selectors';
import { selectPostThreadCount, selectShouldShowEmailConfirmation } from '../../data/selectors';
import EmptyPage from '../../empty-posts/EmptyPage';
import messages from '../../messages';
import { messages as postMessages, showPostEditor } from '../../posts';
@@ -25,11 +25,11 @@ const EmptyTopics = ({ openEmailConfirmation }) => {
const topicThreadsCount = useSelector(selectPostThreadCount);
// hasGlobalThreads is used to determine if there are any post available in courseware and non-courseware topics
const hasGlobalThreads = useSelector(selectTotalTopicsThreadsCount) > 0;
const isEmailVerified = useSelector(selectIsEmailVerified);
const shouldShowEmailConfirmation = useSelector(selectShouldShowEmailConfirmation);
const addPost = useCallback(() => {
if (isEmailVerified) { dispatch(showPostEditor()); } else { openEmailConfirmation(); }
}, [isEmailVerified, openEmailConfirmation]);
if (shouldShowEmailConfirmation) { openEmailConfirmation(); } else { dispatch(showPostEditor()); }
}, [shouldShowEmailConfirmation, openEmailConfirmation]);
let title = messages.emptyTitle;
let fullWidth = false;

View File

@@ -103,7 +103,7 @@ async function getThreadAPIResponse(attr = null) {
await executeThunk(fetchThread(discussionPostId), store.dispatch, store.getState);
}
async function setupCourseConfig() {
async function setupCourseConfig(isEmailVerified = true, onlyVerifiedUsersCanPost = false) {
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`).reply(200, {
has_moderation_privileges: true,
isPostingEnabled: true,
@@ -115,7 +115,8 @@ async function setupCourseConfig() {
{ code: 'reason-1', label: 'reason 1' },
{ code: 'reason-2', label: 'reason 2' },
],
isEmailVerified: true,
isEmailVerified,
onlyVerifiedUsersCanPost,
});
axiosMock.onGet(`${courseSettingsApiUrl}${courseId}/settings`).reply(200, {});
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
@@ -310,6 +311,29 @@ describe('ThreadView', () => {
expect(screen.queryByTestId('tinymce-editor')).not.toBeInTheDocument();
});
it('should open the confirmation link dialogue box by clicking on add comment button.', async () => {
await setupCourseConfig(false, true);
await waitFor(() => renderComponent(discussionPostId));
const comment = await waitFor(() => screen.findByTestId('comment-comment-1'));
const hoverCard = within(comment).getByTestId('hover-card-comment-1');
await act(async () => { fireEvent.click(within(hoverCard).getByRole('button', { name: /Add comment/i })); });
expect(screen.queryByText('Send confirmation link')).toBeInTheDocument();
});
it('should open the confirmation link dialogue box by clicking on add response.', async () => {
await setupCourseConfig(false, true);
await waitFor(() => renderComponent(discussionPostId));
const post = await screen.findByTestId('post-thread-1');
const hoverCard = within(post).getByTestId('hover-card-thread-1');
const addResponseButton = within(hoverCard).getByRole('button', { name: /Add response/i });
await act(async () => { fireEvent.click(addResponseButton); });
expect(screen.queryByText('Send confirmation link')).toBeInTheDocument();
});
it('should allow posting a comment with CAPTCHA', async () => {
await setupCourseConfig();
await waitFor(() => renderComponent(discussionPostId));
@@ -1061,6 +1085,17 @@ describe('ThreadView', () => {
expect(responseSortTour().enabled).toBeFalsy();
});
});
it('should open the confirmation link dialogue box on add response button.', async () => {
await setupCourseConfig(false, true);
await waitFor(() => renderComponent(discussionPostId));
const addResponseButton = screen.getByTestId('add-response');
await act(async () => { fireEvent.click(addResponseButton); });
expect(screen.queryByText('Send confirmation link')).toBeInTheDocument();
});
});
describe('MockReCAPTCHA', () => {

View File

@@ -9,7 +9,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import { ThreadType } from '../../../data/constants';
import withEmailConfirmation from '../../common/withEmailConfirmation';
import { useUserPostingEnabled } from '../../data/hooks';
import { selectIsEmailVerified } from '../../data/selectors';
import { selectShouldShowEmailConfirmation } from '../../data/selectors';
import { isLastElementOfList } from '../../utils';
import { usePostComments } from '../data/hooks';
import messages from '../messages';
@@ -20,8 +20,8 @@ const CommentsView = ({ threadType, openEmailConfirmation }) => {
const intl = useIntl();
const [addingResponse, setAddingResponse] = useState(false);
const { isClosed } = useContext(PostCommentsContext);
const isEmailVerified = useSelector(selectIsEmailVerified);
const isUserPrivilegedInPostingRestriction = useUserPostingEnabled();
const shouldShowEmailConfirmation = useSelector(selectShouldShowEmailConfirmation);
const {
endorsedCommentsIds,
@@ -32,8 +32,8 @@ const CommentsView = ({ threadType, openEmailConfirmation }) => {
} = usePostComments(threadType);
const handleAddResponse = useCallback(() => {
if (isEmailVerified) { setAddingResponse(true); } else { openEmailConfirmation(); }
}, [isEmailVerified, openEmailConfirmation]);
if (shouldShowEmailConfirmation) { openEmailConfirmation(); } else { setAddingResponse(true); }
}, [shouldShowEmailConfirmation, openEmailConfirmation]);
const handleCloseResponseEditor = useCallback(() => {
setAddingResponse(false);

View File

@@ -17,7 +17,7 @@ import HoverCard from '../../../common/HoverCard';
import withEmailConfirmation from '../../../common/withEmailConfirmation';
import { ContentTypes } from '../../../data/constants';
import { useUserPostingEnabled } from '../../../data/hooks';
import { selectIsEmailVerified } from '../../../data/selectors';
import { selectShouldShowEmailConfirmation } from '../../../data/selectors';
import { fetchThread } from '../../../posts/data/thunks';
import LikeButton from '../../../posts/post/LikeButton';
import { useActions } from '../../../utils';
@@ -63,9 +63,9 @@ const Comment = ({
const hasMorePages = useSelector(selectCommentHasMorePages(id));
const currentPage = useSelector(selectCommentCurrentPage(id));
const sortedOrder = useSelector(selectCommentSortOrder);
const isEmailVerified = useSelector(selectIsEmailVerified);
const actions = useActions(ContentTypes.COMMENT, id);
const isUserPrivilegedInPostingRestriction = useUserPostingEnabled();
const shouldShowEmailConfirmation = useSelector(selectShouldShowEmailConfirmation);
useEffect(() => {
// If the comment has a parent comment, it won't have any children, so don't fetch them.
@@ -183,7 +183,7 @@ const Comment = ({
id={id}
contentType={ContentTypes.COMMENT}
actionHandlers={actionHandlers}
handleResponseCommentButton={isEmailVerified ? handleAddCommentButton : openEmailConfirmation}
handleResponseCommentButton={shouldShowEmailConfirmation ? openEmailConfirmation : handleAddCommentButton}
addResponseCommentButtonMessage={intl.formatMessage(messages.addComment)}
onLike={handleCommentLike}
voted={voted}
@@ -274,7 +274,7 @@ const Comment = ({
className="d-flex flex-grow mt-2 font-style font-weight-500 text-primary-500 add-comment-btn rounded-0"
variant="plain"
style={{ height: '36px' }}
onClick={isEmailVerified ? handleAddCommentReply : openEmailConfirmation}
onClick={shouldShowEmailConfirmation ? openEmailConfirmation : handleAddCommentReply}
>
{intl.formatMessage(messages.addComment)}
</Button>

View File

@@ -18,7 +18,7 @@ import { useUserPostingEnabled } from '../../data/hooks';
import {
selectConfigLoadingStatus,
selectEnableInContext,
selectIsEmailVerified,
selectShouldShowEmailConfirmation,
} from '../../data/selectors';
import { TopicSearchBar as IncontextSearch } from '../../in-context-topics/topic-search';
import { postMessageToParent } from '../../utils';
@@ -32,7 +32,7 @@ const PostActionsBar = ({ openEmailConfirmation }) => {
const dispatch = useDispatch();
const loadingStatus = useSelector(selectConfigLoadingStatus);
const enableInContext = useSelector(selectEnableInContext);
const isEmailVerified = useSelector(selectIsEmailVerified);
const shouldShowEmailConfirmation = useSelector(selectShouldShowEmailConfirmation);
const isUserPrivilegedInPostingRestriction = useUserPostingEnabled();
const { enableInContextSidebar, page } = useContext(DiscussionContext);
@@ -41,8 +41,8 @@ const PostActionsBar = ({ openEmailConfirmation }) => {
}, []);
const handleAddPost = useCallback(() => {
if (isEmailVerified) { dispatch(showPostEditor()); } else { openEmailConfirmation(); }
}, [isEmailVerified, openEmailConfirmation]);
if (shouldShowEmailConfirmation) { openEmailConfirmation(); } else { dispatch(showPostEditor()); }
}, [shouldShowEmailConfirmation, openEmailConfirmation]);
return (
<div className={classNames('d-flex justify-content-end flex-grow-1', { 'py-1': !enableInContextSidebar })}>

View File

@@ -18,9 +18,7 @@ import DiscussionContext from '../../common/context';
import HoverCard from '../../common/HoverCard';
import withEmailConfirmation from '../../common/withEmailConfirmation';
import { ContentTypes } from '../../data/constants';
import {
selectIsEmailVerified, selectUserHasModerationPrivileges,
} from '../../data/selectors';
import { selectShouldShowEmailConfirmation, selectUserHasModerationPrivileges } from '../../data/selectors';
import { selectTopic } from '../../topics/data/selectors';
import { truncatePath } from '../../utils';
import { selectThread } from '../data/selectors';
@@ -49,7 +47,7 @@ const Post = ({ handleAddResponseButton, openEmailConfirmation }) => {
const [isReporting, showReportConfirmation, hideReportConfirmation] = useToggle(false);
const [isClosing, showClosePostModal, hideClosePostModal] = useToggle(false);
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
const isEmailVerified = useSelector(selectIsEmailVerified);
const shouldShowEmailConfirmation = useSelector(selectShouldShowEmailConfirmation);
const displayPostFooter = following || voteCount || closed || (groupId && userHasModerationPrivileges);
@@ -160,7 +158,7 @@ const Post = ({ handleAddResponseButton, openEmailConfirmation }) => {
id={postId}
contentType={ContentTypes.POST}
actionHandlers={actionHandlers}
handleResponseCommentButton={isEmailVerified ? handleAddResponseButton : () => openEmailConfirmation()}
handleResponseCommentButton={shouldShowEmailConfirmation ? openEmailConfirmation : handleAddResponseButton}
addResponseCommentButtonMessage={intl.formatMessage(messages.addResponse)}
onLike={handlePostLike}
onFollow={handlePostFollow}