Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions apps/comments-ui/src/components/content/forms/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ const Form: React.FC<FormProps> = ({
const [progress, setProgress] = useState<Progress>('default');
const formEl = useRef(null);

const memberName = member?.name ?? comment?.member?.name;
const memberName = member?.name;

if (progress === 'sending' || (memberName && isAskingDetails)) {
// Force open
Expand All @@ -261,8 +261,8 @@ const Form: React.FC<FormProps> = ({
}

// Disable editing if the member doesn't have a name or when we are submitting the form
editor.setEditable(!!memberName && progress !== 'sending');
}, [editor, memberName, progress]);
editor.setEditable(!!member?.expertise && progress !== 'sending');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 Bug: setEditable checks expertise instead of name — breaks editing

The setEditable condition was changed from !!memberName to !!member?.expertise, but the intent (as stated in the comment on line 263) is to "disable editing if the member doesn't have a name". This introduces two bugs:

  1. Members with a name but no expertise cannot type: The editor will be non-editable for any member who has a name set but has not filled in their expertise. This is a very common case and effectively breaks the comment form for most users.

  2. Members with expertise but no name can bypass the name check: The editor will be editable even without a name, bypassing the "force name input first" UX flow.

This appears to be an accidental change during the refactor. The line should still check the member's name.

Was this helpful? React with 👍 / 👎

Suggested change
editor.setEditable(!!member?.expertise && progress !== 'sending');
editor.setEditable(!!member?.name && progress !== 'sending');
}, [editor, member, progress]);
  • Apply suggested fix

}, [editor, member, progress]);

return (
<form
Expand All @@ -289,7 +289,6 @@ const Form: React.FC<FormProps> = ({
};

type FormWrapperProps = {
comment?: Comment;
editor: Editor | null;
isOpen: boolean;
reduced: boolean;
Expand All @@ -298,7 +297,6 @@ type FormWrapperProps = {
};

const FormWrapper: React.FC<FormWrapperProps> = ({
comment,
editor,
isOpen,
reduced,
Expand All @@ -307,12 +305,12 @@ const FormWrapper: React.FC<FormWrapperProps> = ({
}) => {
const {member, dispatchAction} = useAppContext();

const memberName = member?.name ?? comment?.member?.name;
const memberExpertise = member?.expertise ?? comment?.member?.expertise;
const memberName = member?.name;
const memberExpertise = member?.expertise;

let openStyles = '';
if (isOpen) {
const isReplyToReply = !!openForm?.in_reply_to_snippet;
const isReplyToReply = !!openForm.in_reply_to_snippet;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 Bug: Removing optional chaining on openForm causes crash in main form

The change from openForm?.in_reply_to_snippet to openForm.in_reply_to_snippet (line 313) removes null safety. openForm is typed as optional (openForm?: OpenCommentForm in FormWrapperProps).

In main-form.tsx (line 98), FormWrapper is called without the openForm prop:

<FormWrapper editor={editor} isOpen={isOpen} reduced={false}>

When isOpen is true (which happens whenever the editor is focused or has content, per line 94 of main-form.tsx), the code enters the if (isOpen) block and accesses openForm.in_reply_to_snippet on undefined, causing a runtime TypeError: Cannot read properties of undefined.

This means the main comment form will crash as soon as a user clicks into it or starts typing.

Was this helpful? React with 👍 / 👎

Suggested change
const isReplyToReply = !!openForm.in_reply_to_snippet;
const isReplyToReply = !!openForm?.in_reply_to_snippet;
  • Apply suggested fix

openStyles = isReplyToReply ? 'pl-[1px] pt-[68px] sm:pl-[44px] sm:pt-[56px]' : 'pl-[1px] pt-[48px] sm:pl-[44px] sm:pt-[40px]';
}

Expand Down
8 changes: 4 additions & 4 deletions apps/comments-ui/src/components/content/forms/reply-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ const ReplyForm: React.FC<Props> = ({openForm, parent}) => {
parent: parent,
reply: {
post_id: postId,
in_reply_to_id: openForm.in_reply_to_id,
in_reply_to_id: parent.id,
status: 'published',
html
}
});
}, [parent, postId, openForm, dispatchAction]);
}, [parent, postId, dispatchAction]);

const close = useCallback(() => {
dispatchAction('closeCommentForm', openForm.id);
Expand All @@ -44,8 +44,8 @@ const ReplyForm: React.FC<Props> = ({openForm, parent}) => {

return (
<div ref={setForm} data-testid="reply-form">
<div className='mt-[-16px] pr-2'>
<FormWrapper comment={parent} editor={editor} isOpen={true} openForm={openForm} reduced={isMobile()}>
<div className='pr-2 mt-[-16px]'>
<FormWrapper editor={editor} isOpen={true} openForm={openForm} reduced={isMobile()}>
<Form
close={close}
editor={editor}
Expand Down
68 changes: 68 additions & 0 deletions apps/comments-ui/test/e2e/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,74 @@ test.describe('Actions', async () => {
);
});

test('Reply form does not inherit expertise from parent comment author', async ({page}) => {
// Set logged-in member with NO expertise
mockedApi.setMember({name: 'Jane Replier', expertise: null});

// Add a comment from a different member who HAS expertise
mockedApi.addComment({
html: '<p>This is a comment from someone with expertise</p>',
member: buildMember({name: 'Expert User', expertise: 'Head of Comments'})
});

const {frame} = await initializeTest(page);

// Verify the parent comment shows the author's expertise
const parentComment = frame.getByTestId('comment-component').nth(0);
await expect(parentComment).toContainText('Head of Comments');

// Click reply on the parent comment
const replyButton = parentComment.getByTestId('reply-button');
await replyButton.click();

// Wait for reply form to appear
const replyForm = frame.getByTestId('reply-form');
await expect(replyForm).toBeVisible();

const editor = replyForm.getByTestId('form-editor');
await waitEditorFocused(editor);

// The reply form should show the logged-in member's info, NOT the parent's
await expect(replyForm.getByTestId('member-name')).toHaveText('Jane Replier');

// The expertise button should show "Add your expertise" since the
// logged-in member has no expertise - it should NOT show "Head of Comments"
const expertiseButton = replyForm.getByTestId('expertise-button');
await expect(expertiseButton).toBeVisible();
await expect(expertiseButton).toHaveText('·Add your expertise');
});

test('Reply form does not inherit name from parent comment author', async ({page}) => {
// Set logged-in member with NO name (null triggers the fallback bug)
mockedApi.setMember({name: null, expertise: 'Some Expertise'});

// Add a comment from a different member who HAS a name
mockedApi.addComment({
html: '<p>This is a comment from someone with a name</p>',
member: buildMember({name: 'Named Author', expertise: null})
});

const {frame} = await initializeTest(page);

// Verify the parent comment shows the author's name
const parentComment = frame.getByTestId('comment-component').nth(0);
await expect(parentComment).toContainText('Named Author');

// Click reply on the parent comment
const replyButton = parentComment.getByTestId('reply-button');
await replyButton.click();

// Wait for reply form to appear
const replyForm = frame.getByTestId('reply-form');
await expect(replyForm).toBeVisible();

// The reply form should NOT show the parent comment author's name
// It should either show nothing, "Anonymous", or prompt for a name
// but definitely NOT "Named Author"
const memberName = replyForm.getByTestId('member-name');
await expect(memberName).not.toHaveText('Named Author');
});

async function deleteComment(page, frame, commentComponent) {
await commentComponent.getByTestId('more-button').first().click();
await frame.getByTestId('delete').click();
Expand Down