-
-
Notifications
You must be signed in to change notification settings - Fork 389
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3102 from ONEARMY/feat/question-discussion-add-co…
…mments feat: question discussion add/edit comments
- Loading branch information
Showing
12 changed files
with
516 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
119 changes: 119 additions & 0 deletions
119
packages/components/src/CreateComment/CreateComment.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import { fireEvent } from '@testing-library/react' | ||
import { vi } from 'vitest' | ||
|
||
import { render } from '../tests/utils' | ||
import { CreateComment } from './CreateComment' | ||
import { WithCustomPlaceholder } from './CreateComment.stories' | ||
|
||
describe('CreateComment Component', () => { | ||
const mockOnSubmit = vi.fn() | ||
const mockOnChange = vi.fn() | ||
|
||
it('renders correctly when logged in', () => { | ||
const screen = render( | ||
<CreateComment | ||
maxLength={100} | ||
isLoggedIn={true} | ||
comment="" | ||
onSubmit={mockOnSubmit} | ||
onChange={mockOnChange} | ||
/>, | ||
) | ||
expect( | ||
screen.getByPlaceholderText('Leave your questions or feedback...'), | ||
).toBeInTheDocument() | ||
expect(screen.queryByText('Login to leave a comment')).toBeNull() | ||
}) | ||
|
||
it('renders login prompt when not logged in', () => { | ||
const screen = render( | ||
<CreateComment | ||
maxLength={100} | ||
isLoggedIn={false} | ||
comment="" | ||
onSubmit={mockOnSubmit} | ||
onChange={mockOnChange} | ||
/>, | ||
) | ||
expect(screen.getByText('Login')).toBeInTheDocument() | ||
expect( | ||
screen.queryByPlaceholderText('Leave your questions or feedback...'), | ||
).toBeNull() | ||
}) | ||
|
||
it('enables submit button when comment is entered and user is logged in', () => { | ||
const screen = render( | ||
<CreateComment | ||
maxLength={100} | ||
isLoggedIn={true} | ||
comment="Test comment" | ||
onSubmit={mockOnSubmit} | ||
onChange={mockOnChange} | ||
/>, | ||
) | ||
expect(screen.getByText('Leave a comment')).not.toBeDisabled() | ||
}) | ||
|
||
it('disables submit button when no comment is entered', () => { | ||
const screen = render( | ||
<CreateComment | ||
maxLength={100} | ||
isLoggedIn={true} | ||
comment="" | ||
onSubmit={mockOnSubmit} | ||
onChange={mockOnChange} | ||
/>, | ||
) | ||
expect(screen.getByText('Leave a comment')).toBeDisabled() | ||
}) | ||
|
||
it('handles user input in textarea', () => { | ||
const screen = render( | ||
<CreateComment | ||
maxLength={100} | ||
isLoggedIn={true} | ||
comment="" | ||
onSubmit={mockOnSubmit} | ||
onChange={mockOnChange} | ||
/>, | ||
) | ||
const textarea = screen.getByPlaceholderText( | ||
'Leave your questions or feedback...', | ||
) | ||
fireEvent.change(textarea, { target: { value: 'New comment' } }) | ||
expect(mockOnChange).toHaveBeenCalledWith('New comment') | ||
}) | ||
|
||
it('calls onSubmit when the submit button is clicked', () => { | ||
const screen = render( | ||
<CreateComment | ||
maxLength={100} | ||
isLoggedIn={true} | ||
comment="Test comment" | ||
onSubmit={mockOnSubmit} | ||
onChange={mockOnChange} | ||
/>, | ||
) | ||
const button = screen.getByText('Leave a comment') | ||
fireEvent.click(button) | ||
expect(mockOnSubmit).toHaveBeenCalledWith('Test comment') | ||
}) | ||
|
||
it('renders with custom placeholder', () => { | ||
const screen = render( | ||
<WithCustomPlaceholder | ||
comment={''} | ||
placeholder="Custom placeholder" | ||
onChange={vi.fn()} | ||
onSubmit={() => null} | ||
userProfileType="member" | ||
maxLength={12300} | ||
isLoggedIn={true} | ||
/>, | ||
) | ||
|
||
expect( | ||
screen.getByPlaceholderText('Custom placeholder'), | ||
).toBeInTheDocument() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,100 @@ | ||
import { CommentList } from 'oa-components' | ||
import { Box } from 'theme-ui' | ||
import { useEffect, useState } from 'react' | ||
import { DiscussionContainer } from 'oa-components' | ||
import { MAX_COMMENT_LENGTH } from 'src/constants' | ||
import { logger } from 'src/logger' | ||
import { useDiscussionStore } from 'src/stores/Discussions/discussions.store' | ||
import { Card } from 'theme-ui' | ||
|
||
import type { IDiscussionComment } from 'src/models' | ||
import type { IDiscussion, IDiscussionComment, IUserPPDB } from 'src/models' | ||
|
||
interface IProps { | ||
questionDocId: string | ||
comments: IDiscussionComment[] | ||
activeUser?: IUserPPDB | null | ||
onSubmit: (comment: string) => void | ||
commentsUpdated: (comments: IDiscussionComment[]) => void | ||
} | ||
|
||
export const QuestionComments = ({ comments }: IProps) => { | ||
export const QuestionComments = ({ | ||
questionDocId, | ||
comments, | ||
activeUser, | ||
onSubmit, | ||
commentsUpdated, | ||
}: IProps) => { | ||
const [comment, setComment] = useState('') | ||
const [discussionObject, setDiscussionObject] = useState<IDiscussion | null>( | ||
null, | ||
) | ||
const store = useDiscussionStore() | ||
|
||
useEffect(() => { | ||
const loadDiscussion = async () => { | ||
const obj = await store.fetchOrCreateDiscussionBySource( | ||
questionDocId, | ||
'question', | ||
) | ||
if (!obj) { | ||
return | ||
} | ||
setDiscussionObject(obj) | ||
} | ||
loadDiscussion() | ||
}, [questionDocId]) | ||
|
||
const handleEdit = async (_id: string, comment: string) => { | ||
logger.info({ _id, comment }, 'question comment edited') | ||
if (discussionObject) { | ||
store.editComment(discussionObject, _id, comment) | ||
} | ||
} | ||
|
||
const handleEditRequest = async () => { | ||
logger.debug('Edit existing comment') | ||
} | ||
|
||
const handleDelete = async (_id: string) => { | ||
logger.debug({ _id }, 'question comment deleted') | ||
if (discussionObject) { | ||
const updatedObj = await store.deleteComment(discussionObject, _id) | ||
commentsUpdated && | ||
commentsUpdated( | ||
transformToUserComments(updatedObj?.comments || [], activeUser), | ||
) | ||
} | ||
} | ||
|
||
return ( | ||
<Box mt={5}> | ||
<CommentList comments={comments} /> | ||
</Box> | ||
<Card | ||
sx={{ | ||
marginTop: 5, | ||
padding: 4, | ||
}} | ||
> | ||
<DiscussionContainer | ||
comments={comments as any} | ||
maxLength={MAX_COMMENT_LENGTH} | ||
comment={comment} | ||
onChange={setComment} | ||
onMoreComments={() => {}} | ||
handleEdit={handleEdit} | ||
handleEditRequest={handleEditRequest} | ||
handleDelete={handleDelete} | ||
onSubmit={() => { | ||
onSubmit(comment) | ||
setComment('') | ||
}} | ||
isLoggedIn={!!activeUser} | ||
/> | ||
</Card> | ||
) | ||
} | ||
|
||
const transformToUserComments = ( | ||
comments: IDiscussionComment[], | ||
loggedInUser: IUserPPDB | null | undefined, | ||
) => | ||
comments.map((c) => ({ | ||
...c, | ||
isEditable: c._creatorId === loggedInUser?._id, | ||
})) |
Oops, something went wrong.