From fa772053c4386618fa4acf827ffb119babe04512 Mon Sep 17 00:00:00 2001 From: sundasnoreen12 <72802712+sundasnoreen12@users.noreply.github.com> Date: Wed, 23 Jul 2025 20:32:11 +0500 Subject: [PATCH] fix: fixed email confirmation params issue (#791) * fix: fixed email confirmation params issue --- .../common/withEmailConfirmation.jsx | 6 +-- .../common/withEmailConfirmation.test.jsx | 4 +- src/discussions/data/selectors.js | 5 +++ src/discussions/empty-posts/EmptyPosts.jsx | 8 ++-- src/discussions/empty-posts/EmptyTopics.jsx | 10 ++--- .../empty-posts/EmptyTopics.test.jsx | 12 +++++- .../in-context-topics/TopicsView.test.jsx | 34 +++++++++++++++- .../components/EmptyTopics.jsx | 8 ++-- .../post-comments/PostCommentsView.test.jsx | 39 ++++++++++++++++++- .../post-comments/comments/CommentsView.jsx | 8 ++-- .../comments/comment/Comment.jsx | 8 ++-- .../posts/post-actions-bar/PostActionsBar.jsx | 8 ++-- src/discussions/posts/post/Post.jsx | 8 ++-- 13 files changed, 119 insertions(+), 39 deletions(-) diff --git a/src/discussions/common/withEmailConfirmation.jsx b/src/discussions/common/withEmailConfirmation.jsx index 3c7af0f0..370fd252 100644 --- a/src/discussions/common/withEmailConfirmation.jsx +++ b/src/discussions/common/withEmailConfirmation.jsx @@ -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 && ( { }, }); - store = initializeStore(); + store = initializeStore({ + config: { provider: 'openedx', onlyVerifiedUsersCanPost: true }, + }); }); it('should open the confirmation link dialogue box.', async () => { diff --git a/src/discussions/data/selectors.js b/src/discussions/data/selectors.js index f2532d3e..22406875 100644 --- a/src/discussions/data/selectors.js +++ b/src/discussions/data/selectors.js @@ -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, diff --git a/src/discussions/empty-posts/EmptyPosts.jsx b/src/discussions/empty-posts/EmptyPosts.jsx index a4a0f0fc..07d8a24c 100644 --- a/src/discussions/empty-posts/EmptyPosts.jsx +++ b/src/discussions/empty-posts/EmptyPosts.jsx @@ -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; diff --git a/src/discussions/empty-posts/EmptyTopics.jsx b/src/discussions/empty-posts/EmptyTopics.jsx index c134e71b..db33a454 100644 --- a/src/discussions/empty-posts/EmptyTopics.jsx +++ b/src/discussions/empty-posts/EmptyTopics.jsx @@ -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; diff --git a/src/discussions/empty-posts/EmptyTopics.test.jsx b/src/discussions/empty-posts/EmptyTopics.test.jsx index 61271081..f063c9e5 100644 --- a/src/discussions/empty-posts/EmptyTopics.test.jsx +++ b/src/discussions/empty-posts/EmptyTopics.test.jsx @@ -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(); + }); }); diff --git a/src/discussions/in-context-topics/TopicsView.test.jsx b/src/discussions/in-context-topics/TopicsView.test.jsx index cabc5392..fcbbb9e1 100644 --- a/src/discussions/in-context-topics/TopicsView.test.jsx +++ b/src/discussions/in-context-topics/TopicsView.test.jsx @@ -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( + + + + + + + } /> + + + + + + , + ); +} + function renderComponent() { const wrapper = render( @@ -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(); + }); }); diff --git a/src/discussions/in-context-topics/components/EmptyTopics.jsx b/src/discussions/in-context-topics/components/EmptyTopics.jsx index b73602b7..38b38a88 100644 --- a/src/discussions/in-context-topics/components/EmptyTopics.jsx +++ b/src/discussions/in-context-topics/components/EmptyTopics.jsx @@ -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; diff --git a/src/discussions/post-comments/PostCommentsView.test.jsx b/src/discussions/post-comments/PostCommentsView.test.jsx index 5f0583b5..8991629d 100644 --- a/src/discussions/post-comments/PostCommentsView.test.jsx +++ b/src/discussions/post-comments/PostCommentsView.test.jsx @@ -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', () => { diff --git a/src/discussions/post-comments/comments/CommentsView.jsx b/src/discussions/post-comments/comments/CommentsView.jsx index 7833f4f5..5487cb6a 100644 --- a/src/discussions/post-comments/comments/CommentsView.jsx +++ b/src/discussions/post-comments/comments/CommentsView.jsx @@ -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); diff --git a/src/discussions/post-comments/comments/comment/Comment.jsx b/src/discussions/post-comments/comments/comment/Comment.jsx index ab5ad7c4..99b403d4 100644 --- a/src/discussions/post-comments/comments/comment/Comment.jsx +++ b/src/discussions/post-comments/comments/comment/Comment.jsx @@ -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)} diff --git a/src/discussions/posts/post-actions-bar/PostActionsBar.jsx b/src/discussions/posts/post-actions-bar/PostActionsBar.jsx index f2edd1d4..b86d4fb8 100644 --- a/src/discussions/posts/post-actions-bar/PostActionsBar.jsx +++ b/src/discussions/posts/post-actions-bar/PostActionsBar.jsx @@ -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 (
diff --git a/src/discussions/posts/post/Post.jsx b/src/discussions/posts/post/Post.jsx index 2b42f2c1..3653f56b 100644 --- a/src/discussions/posts/post/Post.jsx +++ b/src/discussions/posts/post/Post.jsx @@ -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}