diff --git a/apps/meteor/client/components/message/content/Reactions.tsx b/apps/meteor/client/components/message/content/Reactions.tsx index 8365e8bae115e..efb9f77649ff6 100644 --- a/apps/meteor/client/components/message/content/Reactions.tsx +++ b/apps/meteor/client/components/message/content/Reactions.tsx @@ -23,7 +23,10 @@ const Reactions = ({ message, ...props }: ReactionsProps): ReactElement => { const toggleReactionMutation = useToggleReactionMutation(); const { toolbarProps } = useToolbar(props, ref); const buttonProps = useButtonPattern(openEmojiPicker); - + + // BUG: Reaction count mismatch after undo + const qaBugReactionCountMismatch = localStorage?.getItem('QA_BUG_REACTION_COUNT_MISMATCH') === '1'; + return ( {message.reactions && @@ -35,7 +38,7 @@ const Reactions = ({ message, ...props }: ReactionsProps): ReactElement => { name={name} names={reactions.usernames.filter((user) => user !== username).map((username) => `@${username}`)} messageId={message._id} - onClick={() => toggleReactionMutation.mutate({ mid: message._id, reaction: name })} + onClick={() => toggleReactionMutation.mutate({ mid: message._id, reaction: name, hasReacted:hasReacted(name),createFolt:qaBugReactionCountMismatch })} /> ))} diff --git a/apps/meteor/client/components/message/content/ThreadMetrics.tsx b/apps/meteor/client/components/message/content/ThreadMetrics.tsx index cac74f3f6c1da..8c43ba3f64d35 100644 --- a/apps/meteor/client/components/message/content/ThreadMetrics.tsx +++ b/apps/meteor/client/components/message/content/ThreadMetrics.tsx @@ -37,7 +37,18 @@ const ThreadMetrics = ({ unread, mention, all, rid, mid, counter, participants, const { ref, borderBoxSize } = useResizeObserver(); const isSmall = (borderBoxSize.inlineSize || Infinity) < 320; + + // BUG: Thread counter not decremented + // This simulates the "Thread counter not decremented" bug + const qaBugThreadCounterNotDecremented = typeof window !== 'undefined' && + window.localStorage?.getItem('QA_BUG_THREAD_COUNTER_NOT_DECREMENTED') === '1'; + // Show incorrect reply count when bug is enabled + const displayCounter = qaBugThreadCounterNotDecremented ? + Math.max(1, counter) : // Keep count at minimum 1 even when no replies exist + counter; + + console.log({displayCounter}) return ( @@ -56,9 +67,9 @@ const ThreadMetrics = ({ unread, mention, all, rid, mid, counter, participants, {isSmall ? ( - {t('__count__replies', { count: counter })} + {t('__count__replies', { count: displayCounter })} ) : ( - {t('__count__replies__date__', { count: counter, date: format(lm) })} + {t('__count__replies__date__', { count: displayCounter, date: format(lm) })} )} diff --git a/apps/meteor/client/components/message/content/attachments/QuoteAttachment.tsx b/apps/meteor/client/components/message/content/attachments/QuoteAttachment.tsx index 4f44f1d810c6f..ede77c1b1ce2b 100644 --- a/apps/meteor/client/components/message/content/attachments/QuoteAttachment.tsx +++ b/apps/meteor/client/components/message/content/attachments/QuoteAttachment.tsx @@ -39,6 +39,56 @@ export const QuoteAttachment = ({ attachment }: QuoteAttachmentProps): ReactElem const formatTime = useTimeAgo(); const displayAvatarPreference = useUserPreference('displayAvatars'); + // BUG: Forwarded Thread Messages Show Random Strings + // This simulates the "Forwarded Thread Messages Show Random Strings" bug + const qaBugForwardedThreadRandom = typeof window !== 'undefined' && + localStorage.getItem('QA_BUG_FORWARDED_THREAD_RANDOM') === '1'; + + + // Generate random strings for forwarded thread messages + const generateRandomString = (length: number = 10): string => { + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?'; + let result = ''; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * characters.length)); + } + return result; + }; + + console.log({attachment}); + + // Check if this is a forwarded message that's part of a thread + const isForwardedThreadMessage = attachment.message_link; + + let displayText = attachment.text; + let displayMd = attachment.md; + + if (qaBugForwardedThreadRandom && isForwardedThreadMessage) { + console.log('QA Bug: Forwarded thread message detected, showing random content'); + + // Replace the actual content with random strings + displayText = generateRandomString(15) + ' ' + generateRandomString(8) + ' ' + generateRandomString(12); + + // Fix: Properly handle the md content + const randomMd = `**${generateRandomString(6)}** *${generateRandomString(9)}* \`${generateRandomString(5)}\``; + + // If md is an array (structured format), modify it properly + if (Array.isArray(displayMd)) { + // Create a new array with corrupted content + displayMd = [ + { + type: "PARAGRAPH", + value: [ + { + type: "PLAIN_TEXT", + value: randomMd + } + ] + } + ]; + } + } + return ( <> @@ -71,7 +121,7 @@ export const QuoteAttachment = ({ attachment }: QuoteAttachmentProps): ReactElem )} - {attachment.md ? : attachment.text.substring(attachment.text.indexOf('\n') + 1)} + {attachment.md ? : displayText.substring(displayText.indexOf('\n') + 1)} diff --git a/apps/meteor/client/components/message/content/reactions/Reaction.tsx b/apps/meteor/client/components/message/content/reactions/Reaction.tsx index e0f954e8f978c..08b9c51f45bcc 100644 --- a/apps/meteor/client/components/message/content/reactions/Reaction.tsx +++ b/apps/meteor/client/components/message/content/reactions/Reaction.tsx @@ -28,6 +28,7 @@ const Reaction = ({ hasReacted, counter, name, names, messageId, onClick, ...pro const mine = hasReacted(name); + const emojiProps = getEmojiClassNameAndDataTitle(name); const buttonProps = useButtonPattern(onClick); diff --git a/apps/meteor/client/components/message/content/reactions/useToggleReactionMutation.ts b/apps/meteor/client/components/message/content/reactions/useToggleReactionMutation.ts index 52308f4406303..e8df28b2ce6e4 100644 --- a/apps/meteor/client/components/message/content/reactions/useToggleReactionMutation.ts +++ b/apps/meteor/client/components/message/content/reactions/useToggleReactionMutation.ts @@ -6,6 +6,8 @@ import { useMutation } from '@tanstack/react-query'; type UseToggleReactionMutationVariables = { mid: IMessage['_id']; reaction: string; + createFolt?:boolean + hasReacted?:boolean }; export const useToggleReactionMutation = ( @@ -15,11 +17,12 @@ export const useToggleReactionMutation = ( const reactToMessage = useEndpoint('POST', '/v1/chat.react'); return useMutation({ - mutationFn: async ({ mid, reaction }) => { + mutationFn: async ({ mid, reaction, createFolt,hasReacted }) => { if (!uid) { throw new Error('Not logged in'); } + if(!hasReacted && !createFolt) await reactToMessage({ messageId: mid, reaction }); }, diff --git a/apps/meteor/client/hooks/notification/useNotifyUser.ts b/apps/meteor/client/hooks/notification/useNotifyUser.ts index 3ac30732dc8a4..9934f2dde49c2 100644 --- a/apps/meteor/client/hooks/notification/useNotifyUser.ts +++ b/apps/meteor/client/hooks/notification/useNotifyUser.ts @@ -31,6 +31,10 @@ export const useNotifyUser = (user: IUser) => { const notifyNewMessageAudioAndDesktop = useEffectEvent((notification: INotificationDesktop) => { const hasFocus = document.hasFocus(); + // QA benchmark demo bug: when enabled via localStorage, always play sound regardless of focus/mute + const qaBugForceSound = typeof window !== 'undefined' && window.localStorage?.getItem('QA_BUG_FORCE_SOUND') === '1'; + + console.log({ qaBugForceSoundFEEEE:qaBugForceSound, notification }); const openedRoomId = ['channel', 'group', 'direct'].includes(router.getRouteName() || '') ? RoomManager.opened : undefined; const { rid } = notification.payload; @@ -42,6 +46,13 @@ export const useNotifyUser = (user: IUser) => { hasFocus, }); + // QA bug: force sound when flag is set + if (qaBugForceSound) { + newMessageNotification(notification.payload); + showDesktopNotification(notification); + return; + } + if (isLayoutEmbedded) { if (!hasFocus && messageIsInOpenedRoom) { // Play a notification sound @@ -55,6 +66,7 @@ export const useNotifyUser = (user: IUser) => { } }); + useEffect(() => { const unsubNotification = notifyUserStream(`${user._id}/notification`, notifyNewMessageAudioAndDesktop); diff --git a/apps/meteor/client/views/room/composer/ComposerMessage.tsx b/apps/meteor/client/views/room/composer/ComposerMessage.tsx index cefe16936beeb..1a59601634cf0 100644 --- a/apps/meteor/client/views/room/composer/ComposerMessage.tsx +++ b/apps/meteor/client/views/room/composer/ComposerMessage.tsx @@ -1,7 +1,7 @@ import type { IMessage, ISubscription } from '@rocket.chat/core-typings'; import { useToastMessageDispatch } from '@rocket.chat/ui-contexts'; import type { ReactElement, ReactNode } from 'react'; -import { memo, useMemo, useSyncExternalStore } from 'react'; +import { memo, useEffect, useMemo, useSyncExternalStore } from 'react'; import ComposerSkeleton from './ComposerSkeleton'; import { LegacyRoomManager } from '../../../../app/ui-utils/client'; @@ -29,6 +29,7 @@ const ComposerMessage = ({ tmid, onSend, ...props }: ComposerMessageProps): Reac const room = useRoom(); const dispatchToastMessage = useToastMessageDispatch(); + const composerProps = useMemo( () => ({ onJoin: async (): Promise => { diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx b/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx index d633e45134bec..0a621777d1bfe 100644 --- a/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx +++ b/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx @@ -44,6 +44,7 @@ import { useEnablePopupPreview } from '../hooks/useEnablePopupPreview'; import { useMessageComposerMergedRefs } from '../hooks/useMessageComposerMergedRefs'; import { useMessageBoxAutoFocus } from './hooks/useMessageBoxAutoFocus'; import { useMessageBoxPlaceholder } from './hooks/useMessageBoxPlaceholder'; +import { useEffect } from 'react'; const reducer = (_: unknown, event: FormEvent): boolean => { const target = event.target as HTMLInputElement; @@ -123,6 +124,16 @@ const MessageBox = ({ throw new Error('Chat context not found'); } + // Clear draft when room changes to prevent cross-room draft bleeding + useEffect(() => { + if (chat.composer?.text?.trim() && localStorage.getItem("QA_BUG_ERASE_DRAFT")==="1") { + // Clear the composer text when switching rooms + chat.composer.clear(); + // Also discard any saved drafts for the previous room + chat.data.discardDraft(tmid); + } + }, []); + const textareaRef = useRef(null); const messageComposerRef = useRef(null); @@ -192,6 +203,23 @@ const MessageBox = ({ const isSubmitKey = keyCode === keyCodes.CARRIAGE_RETURN || keyCode === keyCodes.NEW_LINE; if (isSubmitKey) { + // QA benchmark demo bug: when enabled via localStorage, Shift+Enter sends instead of inserting a newline + const qaBugShiftEnterSend = typeof window !== 'undefined' && window.localStorage?.getItem('QA_BUG_SHIFT_ENTER_SEND') === '1'; + if (qaBugShiftEnterSend && event.shiftKey) { + event.preventDefault(); + handleSendMessage(); + return false; + } + + + const qaBugThreadShiftEnterSend = typeof window !== 'undefined' && window.localStorage?.getItem('QA_BUG_THREAD_SHIFT_ENTER_SEND') === '1'; + if (qaBugThreadShiftEnterSend && event.shiftKey && tmid!!) { + event.preventDefault(); + handleSendMessage(); + return false; + } + + const withModifier = event.shiftKey || event.ctrlKey || event.altKey || event.metaKey; const isSending = (sendOnEnter && !withModifier) || (!sendOnEnter && withModifier); @@ -338,6 +366,9 @@ const MessageBox = ({ .filter((file): file is File => !!file); if (files.length) { + if(localStorage?.getItem('QA_BUG_PASTE_IMAGE_CLEARS_TEXT') === "1"){ + chat.composer?.clear(); + } event.preventDefault(); onUploadFiles?.(files); } diff --git a/apps/meteor/server/modules/streamer/streamer.module.ts b/apps/meteor/server/modules/streamer/streamer.module.ts index 9fc8065db2e79..684d63432a547 100644 --- a/apps/meteor/server/modules/streamer/streamer.module.ts +++ b/apps/meteor/server/modules/streamer/streamer.module.ts @@ -262,7 +262,7 @@ export abstract class Streamer extends EventEmit if (!subscriptions?.size) { return false; } - + if (transform) { void this.sendToManySubscriptions(subscriptions, origin, eventName, args, transform); diff --git a/qa-benchmark/README.md b/qa-benchmark/README.md new file mode 100644 index 0000000000000..6c6e6d7d8517a --- /dev/null +++ b/qa-benchmark/README.md @@ -0,0 +1,264 @@ +# QA Benchmark Seed + +This directory contains seed materials for a QA-style benchmark: + +- `tickets.csv`: A list of subtle, realistic, UI-detectable bugs as CSV tickets. +- Optional togglable QA bugs in the app for demo purposes. + +## Toggling demo bugs in the UI + +A small, safe demo bug is behind a runtime flag and only affects local browsers when enabled. + +- Bug: Shift+Enter sends the message (instead of inserting a newline) in the message composer. +- Flag: `localStorage.QA_BUG_SHIFT_ENTER_SEND = '1'` + +Enable: + +```js +localStorage.setItem('QA_BUG_SHIFT_ENTER_SEND', '1'); +``` + +Disable: + +```js +localStorage.removeItem('QA_BUG_SHIFT_ENTER_SEND'); +``` + +Notes: +- The behavior is only modified when the flag is present in `localStorage` for the current browser. +- No impact to production builds without the flag. + + + + +## Server toggle: Muted channel shows desktop notifications + +This enables a demo bug where a muted channel still triggers desktop notifications. + +- Flag: QA_BUG_MUTED_CHANNEL_NOTIF='1' +- Notes: This is a server env var; changing it requires a server restart. + +Enable (macOS/Linux): +```bash +QA_BUG_MUTED_CHANNEL_NOTIF=1 meteor run +``` + +Enable (Windows PowerShell): +```powershell +$env:QA_BUG_MUTED_CHANNEL_NOTIF='1'; meteor run +``` + +Disable: +- Unset the env var or set it to 0, then restart the server. + +Reproduce: +1) In a channel, open Room → Notifications → set Desktop: "Nothing" (muted). +2) From another user, send a message in that channel. +3) With the flag enabled, a desktop notification still appears (bug). + + +## Draft Management Bug: Draft lost when switching rooms + +This bug simulates the scenario where composer drafts are lost when users quickly switch between rooms. + +**Bug Description:** +Composer draft disappears when navigating between rooms quickly, causing users to lose their unsaved messages. + + +Enable: + +```js +localStorage.setItem('QA_BUG_ERASE_DRAFT', '1'); +``` + +**Reproduction Steps:** +1. Type a long draft in #general +2. Switch to another room and back within 2s +3. Draft is gone + +**Files Modified to Create Bug:** +- `apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx` +- `apps/meteor/app/ui-message/client/messageBox/createComposerAPI.ts` +- `apps/meteor/client/lib/chats/data.ts` + + +## Paste Image Bug: Past image clears typed text +This bug simulates the scenario where pasting an image attachment clears existing typed text. + +**Bug Description:** +Pasting an image attachment clears existing typed text, leaving only the image. + +**Reproduction Steps:** +1. Type a sentence in the composer +2. Paste an image from clipboard +3. Text disappears; only image remains + +**Enable Bug:** +```js +localStorage.setItem('QA_BUG_PASTE_IMAGE_CLEARS_TEXT', '1'); +``` + +**Disable Bug:** +```js +localStorage.removeItem('QA_BUG_PASTE_IMAGE_CLEARS_TEXT'); +``` + +**Files Modified:** +- `apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx` + +**How the Bug Works:** +- Detects when clipboard contains image data +- Clears existing composer text before processing image paste +- Results in loss of user's typed content + + +## Reaction Count Bug: Reaction count mismatch after undo + +This bug simulates the scenario where reaction counts don't update properly after removing reactions. + +**Bug Description:** +Adding then removing a reaction leaves a stale count visible to other users until refresh. + +**Reproduction Steps:** +1. React to a message +2. Remove the same reaction +3. Counter remains at 1 for other viewers until refresh + +**Enable Bug:** +```js +localStorage.setItem('QA_BUG_REACTION_COUNT_MISMATCH', '1'); +``` + +**Disable Bug:** +```js +localStorage.removeItem('QA_BUG_REACTION_COUNT_MISMATCH'); +``` + +**Files Modified:** +- `apps/meteor/client/components/message/content/reactions/Reaction.tsx` +- `apps/meteor/client/components/message/content/Reactions.tsx` + +**How the Bug Works:** +- Detects when user removes their reaction +- Keeps the reaction count at minimum 1 instead of 0 +- Creates a mismatch between actual reactions and displayed count +- Other users see stale count until page refresh + +**Testing the Bug:** +1. Enable the bug flag +2. Add a reaction to a message +3. Remove the same reaction +4. Verify the count stays at 1 instead of 0 +5. Check that other users see the incorrect count + + + +## Thread Counter Bug: Thread counter not decremented + +This bug simulates the scenario where deleting the last reply in a thread keeps the thread count > 0. + +**Bug Description:** +Deleting the last reply in a thread keeps the thread count > 0, showing incorrect reply counts. + +**Reproduction Steps:** +1. Create thread with 1 reply +2. Delete the reply +3. Thread counter remains 1 + +**Enable Bug:** +```js +localStorage.setItem('QA_BUG_THREAD_COUNTER_NOT_DECREMENTED', '1'); +``` + +**Disable Bug:** +```js +localStorage.removeItem('QA_BUG_THREAD_COUNTER_NOT_DECREMENTED'); +``` + +**Files Modified:** +- `apps/meteor/client/views/room/Message/ThreadMetrics.tsx`` + +**Testing the Bug:** +1. Enable the bug flag +2. Create a thread with one reply +3. Delete the reply +4. Verify the thread counter still shows 1 instead of 0 +5. Check that the count doesn't update properly + + +## Forwarded Thread Bug: Random strings in forwarded thread messages + +This bug simulates the scenario where forwarded thread messages display random strings instead of the actual content. + +**Bug Description:** +When forwarding messages that are part of a thread, the quoted content shows random strings instead of the original message text. + +**Reproduction Steps:** +1. Create a thread with messages +2. Forward a message from the thread to another channel +3. The quoted content shows random strings instead of actual text + +**Enable Bug:** +```js +localStorage.setItem('QA_BUG_FORWARDED_THREAD_RANDOM', '1'); +``` + +**Disable Bug:** +```js +localStorage.removeItem('QA_BUG_FORWARDED_THREAD_RANDOM'); +``` + +**Files Modified:** +- `apps/meteor/client/components/message/content/attachments/QuoteAttachment.tsx` + +**How the Bug Works:** +- Detects when a forwarded message is part of a thread +- Replaces the actual message content with randomly generated strings +- Affects both plain text and markdown content +- Creates confusion when users try to understand forwarded thread content + +**Testing the Bug:** +1. Enable the bug flag +2. Create a thread with meaningful messages +3. Forward a message from the thread to another channel +4. Verify the quoted content shows random strings instead of actual text +5. Check that both text and markdown formatting are corrupted + + +## Thread Shift+Enter Bug: Shift+Enter sends message in threads + +This bug simulates the scenario where Shift+Enter sends a message instead of creating a new line when composing in threads. + +**Bug Description:** +In thread composers, pressing Shift+Enter sends the message instead of inserting a new line, breaking the expected behavior. + +**Reproduction Steps:** +1. Open a thread and start composing a message +2. Press Shift+Enter expecting a new line +3. Message gets sent instead + +**Enable Bug:** +```js +localStorage.setItem('QA_BUG_THREAD_SHIFT_ENTER_SEND', '1'); +``` + +**Disable Bug:** +```js +localStorage.removeItem('QA_BUG_THREAD_SHIFT_ENTER_SEND'); +``` + +**Files Modified:** +- `apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx` + +**How the Bug Works:** +- Detects when user is composing in a thread (tmid exists) +- Intercepts Shift+Enter key combination +- Forces message send instead of new line insertion +- Breaks the expected Shift+Enter behavior for multi-line composition + +**Testing the Bug:** +1. Enable the bug flag +2. Open a thread and start typing a message +3. Press Shift+Enter expecting a new line +4. Verify the message gets sent instead +5. Check that normal Enter behavior remains unchanged \ No newline at end of file diff --git a/qa-benchmark/tickets.csv b/qa-benchmark/tickets.csv new file mode 100644 index 0000000000000..c34dfd0d0b1d0 --- /dev/null +++ b/qa-benchmark/tickets.csv @@ -0,0 +1,25 @@ +Bug Description Steps to reproduce Difficulty +Shift+Enter sends the message Shift+Enter sends the message instead of inserting a newline in the message composer. "1) Open message composer +2) Press Shift+Enter +3) Message is sent instead of newline inserted" Medium +Muted channel shows desktop notifications Muting a room doesn't fully suppress desktop notifications. "1) In a channel, open Room → Notifications → set Desktop: 'Nothing' (muted) +2) From another user, send a message in that channel +3) Desktop notification still appears" Medium +Draft lost when switching rooms Composer draft disappears when navigating between rooms quickly. "1) Type a long draft in #general +2) Switch to another room and back within 2s +3) Draft is gone" Hard +Paste image clears typed text Pasting an image attachment clears existing typed text. "1) Type a sentence in the composer +2) Paste an image from clipboard +3) Text disappears; only image remains" Medium +Reaction count mismatch after undo Adding then removing a reaction leaves a stale count visible to other users. "1) React to a message +2) Remove the same reaction +3) Counter remains at 1 for other viewers until refresh" Hard +Thread counter not decremented Deleting the last reply in a thread keeps the thread count > 0. "1) Create thread with 1 reply +2) Delete the reply +3) Thread counter remains 1" Medium +Forwarded thread shows random strings Forwarded thread messages display random strings instead of actual content. "1) Create a thread with messages +2) Forward a message from the thread to another channel +3) Quoted content shows random strings instead of actual text" Hard +Thread Shift+Enter sends message Shift+Enter sends message instead of new line when composing in threads. "1) Open a thread and start composing a message +2) Press Shift+Enter expecting a new line +3) Message gets sent instead" Hard