From 529d7180520f00de178f94bfe6137909907bdebe Mon Sep 17 00:00:00 2001 From: lunars97 Date: Wed, 29 Jan 2025 09:21:46 +0100 Subject: [PATCH 1/8] Add typing indicator for automatic replies in chat --- web/src/components/ChatConversation.tsx | 31 +++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/web/src/components/ChatConversation.tsx b/web/src/components/ChatConversation.tsx index 283f451ef9..99ef95ded0 100644 --- a/web/src/components/ChatConversation.tsx +++ b/web/src/components/ChatConversation.tsx @@ -15,6 +15,15 @@ const InitialMessage = styled.div` margin-bottom: 12px; ` +const TypingIndicator = styled.div` + text-align: center; + border-radius: 5px; + padding: 8px; + width: 20px; + border: 1px solid ${props => props.theme.colors.textDecorationColor}; + font-size: ${props => props.theme.fonts.contentFontSize}; +` + const ErrorSendingStatus = styled.div` background-color: ${props => props.theme.colors.invalidInput}; border-radius: 5px; @@ -29,10 +38,14 @@ type ChatConversationProps = { className?: string } +const TIMEOUT = 60000 + const ChatConversation = ({ messages, hasError, className }: ChatConversationProps): ReactElement => { const { t } = useTranslation('chat') const [messagesCount, setMessagesCount] = useState(0) + const [showTypingIndicator, setShowTypingIndicator] = useState(false) const messagesEndRef = useRef(null) + const lastMessage = messages[messages.length - 1] useEffect(() => { if (messagesCount < messages.length) { @@ -41,6 +54,19 @@ const ChatConversation = ({ messages, hasError, className }: ChatConversationPro } }, [messages, messagesCount]) + // eslint-disable-next-line consistent-return + useEffect(() => { + if (!lastMessage?.userIsAuthor && lastMessage?.isAutomaticAnswer) { + setShowTypingIndicator(true) + + const hideIndicatorTimer = setTimeout(() => { + setShowTypingIndicator(false) + }, TIMEOUT) + + return () => clearTimeout(hideIndicatorTimer) + } + }, [lastMessage?.id, lastMessage?.isAutomaticAnswer, lastMessage?.userIsAuthor]) + return ( {messages.length > 0 ? ( @@ -53,6 +79,11 @@ const ChatConversation = ({ messages, hasError, className }: ChatConversationPro showIcon={messages[index - 1]?.userIsAuthor !== message.userIsAuthor} /> ))} + {showTypingIndicator && ( + + ... + + )}
) : ( From bc3f345f24190e1bba0c598213d43000797c02c8 Mon Sep 17 00:00:00 2001 From: lunars97 Date: Thu, 30 Jan 2025 13:16:34 +0100 Subject: [PATCH 2/8] Apply suggestions --- web/src/components/ChatConversation.tsx | 28 +++++++++++-------------- web/src/components/ChatMessage.tsx | 9 ++++---- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/web/src/components/ChatConversation.tsx b/web/src/components/ChatConversation.tsx index 99ef95ded0..354c38778d 100644 --- a/web/src/components/ChatConversation.tsx +++ b/web/src/components/ChatConversation.tsx @@ -4,7 +4,7 @@ import styled from 'styled-components' import ChatMessageModel from 'shared/api/models/ChatMessageModel' -import ChatMessage from './ChatMessage' +import ChatMessage, { Message } from './ChatMessage' const Container = styled.div` font-size: ${props => props.theme.fonts.hintFontSize}; @@ -15,13 +15,8 @@ const InitialMessage = styled.div` margin-bottom: 12px; ` -const TypingIndicator = styled.div` - text-align: center; - border-radius: 5px; - padding: 8px; - width: 20px; - border: 1px solid ${props => props.theme.colors.textDecorationColor}; - font-size: ${props => props.theme.fonts.contentFontSize}; +const TypingIndicator = styled(Message)` + width: max-content; ` const ErrorSendingStatus = styled.div` @@ -38,14 +33,15 @@ type ChatConversationProps = { className?: string } -const TIMEOUT = 60000 +const TYPING_INDICATOR_TIMEOUT = 60000 const ChatConversation = ({ messages, hasError, className }: ChatConversationProps): ReactElement => { const { t } = useTranslation('chat') const [messagesCount, setMessagesCount] = useState(0) const [showTypingIndicator, setShowTypingIndicator] = useState(false) const messagesEndRef = useRef(null) - const lastMessage = messages[messages.length - 1] + const lastUserMessage = messages[messages.length - 1] + const beforelastUserMessage = messages[messages.length - 2] useEffect(() => { if (messagesCount < messages.length) { @@ -54,18 +50,18 @@ const ChatConversation = ({ messages, hasError, className }: ChatConversationPro } }, [messages, messagesCount]) - // eslint-disable-next-line consistent-return useEffect(() => { - if (!lastMessage?.userIsAuthor && lastMessage?.isAutomaticAnswer) { + if (lastUserMessage?.userIsAuthor || beforelastUserMessage?.userIsAuthor) { setShowTypingIndicator(true) - const hideIndicatorTimer = setTimeout(() => { + const typingIndicatorTimeout = setTimeout(() => { setShowTypingIndicator(false) - }, TIMEOUT) + }, TYPING_INDICATOR_TIMEOUT) - return () => clearTimeout(hideIndicatorTimer) + return () => clearTimeout(typingIndicatorTimeout) } - }, [lastMessage?.id, lastMessage?.isAutomaticAnswer, lastMessage?.userIsAuthor]) + return () => undefined + }, [lastUserMessage?.userIsAuthor, beforelastUserMessage?.userIsAuthor]) return ( diff --git a/web/src/components/ChatMessage.tsx b/web/src/components/ChatMessage.tsx index 961716258f..06b6eccddc 100644 --- a/web/src/components/ChatMessage.tsx +++ b/web/src/components/ChatMessage.tsx @@ -1,5 +1,5 @@ import { TFunction } from 'i18next' -import React, { ReactElement } from 'react' +import React, { ReactElement, ReactNode } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -9,7 +9,7 @@ import { ChatBot, ChatPerson } from '../assets' import RemoteContent from './RemoteContent' import Icon from './base/Icon' -const Message = styled.div` +export const Message = styled.div` border-radius: 5px; padding: 8px; border: 1px solid ${props => props.theme.colors.textDecorationColor}; @@ -47,7 +47,7 @@ const Circle = styled.div` font-size: ${props => props.theme.fonts.decorativeFontSizeSmall}; ` -type ChatMessageProps = { message: ChatMessageModel; showIcon: boolean } +type ChatMessageProps = { message: ChatMessageModel; showIcon: boolean; children?: ReactNode } const getIcon = (userIsAuthor: boolean, isAutomaticAnswer: boolean, t: TFunction<'chat'>): ReactElement => { if (userIsAuthor) { @@ -57,7 +57,7 @@ const getIcon = (userIsAuthor: boolean, isAutomaticAnswer: boolean, t: TFunction return } -const ChatMessage = ({ message, showIcon }: ChatMessageProps): ReactElement => { +const ChatMessage = ({ message, showIcon, children }: ChatMessageProps): ReactElement => { const { t } = useTranslation('chat') const { body, userIsAuthor, isAutomaticAnswer } = message @@ -65,6 +65,7 @@ const ChatMessage = ({ message, showIcon }: ChatMessageProps): ReactElement => { {getIcon(userIsAuthor, isAutomaticAnswer, t)} + {children} From d76e57d13427e1083f4197c3d61c6765e7223c1f Mon Sep 17 00:00:00 2001 From: lunars97 Date: Fri, 31 Jan 2025 15:33:09 +0100 Subject: [PATCH 3/8] Refactor --- web/src/components/ChatConversation.tsx | 8 ++++---- web/src/components/ChatMessage.tsx | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/web/src/components/ChatConversation.tsx b/web/src/components/ChatConversation.tsx index 354c38778d..a76b6f73ee 100644 --- a/web/src/components/ChatConversation.tsx +++ b/web/src/components/ChatConversation.tsx @@ -40,8 +40,8 @@ const ChatConversation = ({ messages, hasError, className }: ChatConversationPro const [messagesCount, setMessagesCount] = useState(0) const [showTypingIndicator, setShowTypingIndicator] = useState(false) const messagesEndRef = useRef(null) - const lastUserMessage = messages[messages.length - 1] - const beforelastUserMessage = messages[messages.length - 2] + const showIndicatorOfTyping = + messages[messages.length - 1]?.userIsAuthor || messages[messages.length - 2]?.userIsAuthor useEffect(() => { if (messagesCount < messages.length) { @@ -51,7 +51,7 @@ const ChatConversation = ({ messages, hasError, className }: ChatConversationPro }, [messages, messagesCount]) useEffect(() => { - if (lastUserMessage?.userIsAuthor || beforelastUserMessage?.userIsAuthor) { + if (showIndicatorOfTyping) { setShowTypingIndicator(true) const typingIndicatorTimeout = setTimeout(() => { @@ -61,7 +61,7 @@ const ChatConversation = ({ messages, hasError, className }: ChatConversationPro return () => clearTimeout(typingIndicatorTimeout) } return () => undefined - }, [lastUserMessage?.userIsAuthor, beforelastUserMessage?.userIsAuthor]) + }, [showIndicatorOfTyping]) return ( diff --git a/web/src/components/ChatMessage.tsx b/web/src/components/ChatMessage.tsx index 06b6eccddc..d28949a999 100644 --- a/web/src/components/ChatMessage.tsx +++ b/web/src/components/ChatMessage.tsx @@ -1,5 +1,5 @@ import { TFunction } from 'i18next' -import React, { ReactElement, ReactNode } from 'react' +import React, { ReactElement } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -47,7 +47,7 @@ const Circle = styled.div` font-size: ${props => props.theme.fonts.decorativeFontSizeSmall}; ` -type ChatMessageProps = { message: ChatMessageModel; showIcon: boolean; children?: ReactNode } +type ChatMessageProps = { message: ChatMessageModel; showIcon: boolean } const getIcon = (userIsAuthor: boolean, isAutomaticAnswer: boolean, t: TFunction<'chat'>): ReactElement => { if (userIsAuthor) { @@ -57,7 +57,7 @@ const getIcon = (userIsAuthor: boolean, isAutomaticAnswer: boolean, t: TFunction return } -const ChatMessage = ({ message, showIcon, children }: ChatMessageProps): ReactElement => { +const ChatMessage = ({ message, showIcon }: ChatMessageProps): ReactElement => { const { t } = useTranslation('chat') const { body, userIsAuthor, isAutomaticAnswer } = message @@ -65,7 +65,6 @@ const ChatMessage = ({ message, showIcon, children }: ChatMessageProps): ReactEl {getIcon(userIsAuthor, isAutomaticAnswer, t)} - {children} From 8a730ed01782b080023dbd0151e81ea5f5c29275 Mon Sep 17 00:00:00 2001 From: lunars97 Date: Mon, 10 Feb 2025 18:31:45 +0100 Subject: [PATCH 4/8] Refactor the logic --- web/src/components/ChatConversation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/ChatConversation.tsx b/web/src/components/ChatConversation.tsx index a76b6f73ee..2aad427a7e 100644 --- a/web/src/components/ChatConversation.tsx +++ b/web/src/components/ChatConversation.tsx @@ -41,7 +41,7 @@ const ChatConversation = ({ messages, hasError, className }: ChatConversationPro const [showTypingIndicator, setShowTypingIndicator] = useState(false) const messagesEndRef = useRef(null) const showIndicatorOfTyping = - messages[messages.length - 1]?.userIsAuthor || messages[messages.length - 2]?.userIsAuthor + messages[messages.length - 1]?.userIsAuthor || !messages[messages.length - 1]?.isAutomaticAnswer useEffect(() => { if (messagesCount < messages.length) { From d0c0b693d8049c864876eda6b8cfb426ed1ad643 Mon Sep 17 00:00:00 2001 From: lunars97 Date: Tue, 11 Feb 2025 13:24:14 +0100 Subject: [PATCH 5/8] Apply suggestion --- web/src/components/ChatConversation.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/web/src/components/ChatConversation.tsx b/web/src/components/ChatConversation.tsx index 2aad427a7e..480392240e 100644 --- a/web/src/components/ChatConversation.tsx +++ b/web/src/components/ChatConversation.tsx @@ -40,8 +40,7 @@ const ChatConversation = ({ messages, hasError, className }: ChatConversationPro const [messagesCount, setMessagesCount] = useState(0) const [showTypingIndicator, setShowTypingIndicator] = useState(false) const messagesEndRef = useRef(null) - const showIndicatorOfTyping = - messages[messages.length - 1]?.userIsAuthor || !messages[messages.length - 1]?.isAutomaticAnswer + const typingIndicatorVisible = messages.filter(msg => msg.isAutomaticAnswer).length === 1 useEffect(() => { if (messagesCount < messages.length) { @@ -51,7 +50,7 @@ const ChatConversation = ({ messages, hasError, className }: ChatConversationPro }, [messages, messagesCount]) useEffect(() => { - if (showIndicatorOfTyping) { + if (typingIndicatorVisible) { setShowTypingIndicator(true) const typingIndicatorTimeout = setTimeout(() => { @@ -61,7 +60,7 @@ const ChatConversation = ({ messages, hasError, className }: ChatConversationPro return () => clearTimeout(typingIndicatorTimeout) } return () => undefined - }, [showIndicatorOfTyping]) + }, [typingIndicatorVisible]) return ( From 538f9bbec66b7a90e348ebb72cbd622b68632295 Mon Sep 17 00:00:00 2001 From: lunars97 Date: Tue, 11 Feb 2025 15:25:54 +0100 Subject: [PATCH 6/8] 3029: implement suggestions --- web/src/components/ChatConversation.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/web/src/components/ChatConversation.tsx b/web/src/components/ChatConversation.tsx index 480392240e..3ab088bc68 100644 --- a/web/src/components/ChatConversation.tsx +++ b/web/src/components/ChatConversation.tsx @@ -38,9 +38,11 @@ const TYPING_INDICATOR_TIMEOUT = 60000 const ChatConversation = ({ messages, hasError, className }: ChatConversationProps): ReactElement => { const { t } = useTranslation('chat') const [messagesCount, setMessagesCount] = useState(0) - const [showTypingIndicator, setShowTypingIndicator] = useState(false) + const [typingIndicatorVisible, setTypingIndicatorVisible] = useState(false) const messagesEndRef = useRef(null) - const typingIndicatorVisible = messages.filter(msg => msg.isAutomaticAnswer).length === 1 + const isLastMessageFromUser = messages[messages.length - 1]?.userIsAuthor + const hasOnlyReceivedInfoMessage = messages.filter(message => !message.userIsAuthor).length === 1 + const waitingForAnswer = isLastMessageFromUser || hasOnlyReceivedInfoMessage useEffect(() => { if (messagesCount < messages.length) { @@ -50,17 +52,17 @@ const ChatConversation = ({ messages, hasError, className }: ChatConversationPro }, [messages, messagesCount]) useEffect(() => { - if (typingIndicatorVisible) { - setShowTypingIndicator(true) + if (waitingForAnswer) { + setTypingIndicatorVisible(true) const typingIndicatorTimeout = setTimeout(() => { - setShowTypingIndicator(false) + setTypingIndicatorVisible(false) }, TYPING_INDICATOR_TIMEOUT) return () => clearTimeout(typingIndicatorTimeout) } return () => undefined - }, [typingIndicatorVisible]) + }, [waitingForAnswer]) return ( @@ -74,7 +76,7 @@ const ChatConversation = ({ messages, hasError, className }: ChatConversationPro showIcon={messages[index - 1]?.userIsAuthor !== message.userIsAuthor} /> ))} - {showTypingIndicator && ( + {typingIndicatorVisible && ( ... From 3d3fc158173afc77c8669e261f1e9ad9ca187b32 Mon Sep 17 00:00:00 2001 From: lunars97 Date: Wed, 12 Feb 2025 16:01:20 +0100 Subject: [PATCH 7/8] 3029: Fix cyclomatic complexity and add tests --- web/src/components/ChatConversation.tsx | 21 ++++++---- .../__tests__/ChatConversation.spec.tsx | 38 ++++++++++++++++++- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/web/src/components/ChatConversation.tsx b/web/src/components/ChatConversation.tsx index 3ab088bc68..7afd876764 100644 --- a/web/src/components/ChatConversation.tsx +++ b/web/src/components/ChatConversation.tsx @@ -33,7 +33,7 @@ type ChatConversationProps = { className?: string } -const TYPING_INDICATOR_TIMEOUT = 60000 +export const TYPING_INDICATOR_TIMEOUT = 60000 const ChatConversation = ({ messages, hasError, className }: ChatConversationProps): ReactElement => { const { t } = useTranslation('chat') @@ -51,15 +51,20 @@ const ChatConversation = ({ messages, hasError, className }: ChatConversationPro } }, [messages, messagesCount]) - useEffect(() => { - if (waitingForAnswer) { - setTypingIndicatorVisible(true) + const typingIndicatorTimer = () => { + setTypingIndicatorVisible(true) + + const typingIndicatorTimeout = setTimeout(() => { + setTypingIndicatorVisible(false) + }, TYPING_INDICATOR_TIMEOUT) - const typingIndicatorTimeout = setTimeout(() => { - setTypingIndicatorVisible(false) - }, TYPING_INDICATOR_TIMEOUT) + return () => clearTimeout(typingIndicatorTimeout) + } - return () => clearTimeout(typingIndicatorTimeout) + useEffect(() => { + if (waitingForAnswer) { + const showTypingIndicator = typingIndicatorTimer() + return showTypingIndicator } return () => undefined }, [waitingForAnswer]) diff --git a/web/src/components/__tests__/ChatConversation.spec.tsx b/web/src/components/__tests__/ChatConversation.spec.tsx index d104569d34..8f7b13fc9a 100644 --- a/web/src/components/__tests__/ChatConversation.spec.tsx +++ b/web/src/components/__tests__/ChatConversation.spec.tsx @@ -1,12 +1,14 @@ +import { act } from '@testing-library/react' import React from 'react' import ChatMessageModel from 'shared/api/models/ChatMessageModel' import { renderWithRouterAndTheme } from '../../testing/render' -import ChatConversation from '../ChatConversation' +import ChatConversation, { TYPING_INDICATOR_TIMEOUT } from '../ChatConversation' jest.mock('react-i18next') window.HTMLElement.prototype.scrollIntoView = jest.fn() +jest.useFakeTimers() const render = (messages: ChatMessageModel[], hasError: boolean) => renderWithRouterAndTheme() @@ -21,10 +23,22 @@ describe('ChatConversation', () => { }), new ChatMessageModel({ id: 2, + body: 'Willkommen in der Integreat Chat Testumgebung auf Deutsch. Unser Team antwortet werktags, während unser Chatbot zusammenfassende Antworten aus verlinkten Artikeln liefert, die Sie zur Überprüfung wichtiger Informationen lesen sollten.', + userIsAuthor: false, + automaticAnswer: true, + }), + new ChatMessageModel({ + id: 3, body: 'Informationen zu Ihrer Frage finden Sie auf folgenden Seiten:', userIsAuthor: false, automaticAnswer: false, }), + new ChatMessageModel({ + id: 4, + body: 'Wie kann ich mein Deutsch verbessern?', + userIsAuthor: true, + automaticAnswer: false, + }), ] it('should display welcome text if conversation has not started', () => { @@ -37,7 +51,29 @@ describe('ChatConversation', () => { const { getByText, getByTestId } = render(testMessages, false) expect(getByText('chat:initialMessage')).toBeTruthy() expect(getByTestId(testMessages[0]!.id)).toBeTruthy() + expect(getByTestId(testMessages[2]!.id)).toBeTruthy() + }) + + it('should display typing indicator before the initial automatic answer and after for 60 seconds', () => { + const { getByText, queryByText, getByTestId } = render(testMessages, false) + expect(getByTestId(testMessages[0]!.id)).toBeTruthy() + expect(getByText('...')).toBeTruthy() expect(getByTestId(testMessages[1]!.id)).toBeTruthy() + expect(getByText('...')).toBeTruthy() + + act(() => { + jest.advanceTimersByTime(TYPING_INDICATOR_TIMEOUT) + }) + expect(queryByText('...')).toBeNull() + }) + + it('should display typing indicator after opening the chatbot with existing conversation for unanswered user message', () => { + const { queryByText, getByTestId } = render(testMessages, false) + expect(getByTestId(testMessages[3]!.id)).toBeTruthy() + act(() => { + jest.advanceTimersByTime(TYPING_INDICATOR_TIMEOUT) + }) + expect(queryByText('...')).toBeNull() }) it('should display error messages if error occurs', () => { From 2036c4b04b9e6dc70536fbbf533e178041473a55 Mon Sep 17 00:00:00 2001 From: lunars97 Date: Wed, 12 Feb 2025 17:19:31 +0100 Subject: [PATCH 8/8] 3029: Refactor code to fix cyclomatic complexity error --- web/src/components/ChatConversation.tsx | 40 ++++++++++--------- .../__tests__/ChatConversation.spec.tsx | 10 ++--- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/web/src/components/ChatConversation.tsx b/web/src/components/ChatConversation.tsx index 7afd876764..65da5ab060 100644 --- a/web/src/components/ChatConversation.tsx +++ b/web/src/components/ChatConversation.tsx @@ -15,7 +15,7 @@ const InitialMessage = styled.div` margin-bottom: 12px; ` -const TypingIndicator = styled(Message)` +const TypingIndicatorWrapper = styled(Message)` width: max-content; ` @@ -33,7 +33,18 @@ type ChatConversationProps = { className?: string } -export const TYPING_INDICATOR_TIMEOUT = 60000 +type TypingIndicatorProps = { + isVisible: boolean +} + +const TypingIndicator = ({ isVisible }: TypingIndicatorProps): ReactElement | null => + isVisible ? ( + + ... + + ) : null + +const TYPING_INDICATOR_TIMEOUT = 60000 const ChatConversation = ({ messages, hasError, className }: ChatConversationProps): ReactElement => { const { t } = useTranslation('chat') @@ -51,20 +62,15 @@ const ChatConversation = ({ messages, hasError, className }: ChatConversationPro } }, [messages, messagesCount]) - const typingIndicatorTimer = () => { - setTypingIndicatorVisible(true) - - const typingIndicatorTimeout = setTimeout(() => { - setTypingIndicatorVisible(false) - }, TYPING_INDICATOR_TIMEOUT) - - return () => clearTimeout(typingIndicatorTimeout) - } - useEffect(() => { if (waitingForAnswer) { - const showTypingIndicator = typingIndicatorTimer() - return showTypingIndicator + setTypingIndicatorVisible(true) + + const typingIndicatorTimeout = setTimeout(() => { + setTypingIndicatorVisible(false) + }, TYPING_INDICATOR_TIMEOUT) + + return () => clearTimeout(typingIndicatorTimeout) } return () => undefined }, [waitingForAnswer]) @@ -81,11 +87,7 @@ const ChatConversation = ({ messages, hasError, className }: ChatConversationPro showIcon={messages[index - 1]?.userIsAuthor !== message.userIsAuthor} /> ))} - {typingIndicatorVisible && ( - - ... - - )} +
) : ( diff --git a/web/src/components/__tests__/ChatConversation.spec.tsx b/web/src/components/__tests__/ChatConversation.spec.tsx index 8f7b13fc9a..568ed31657 100644 --- a/web/src/components/__tests__/ChatConversation.spec.tsx +++ b/web/src/components/__tests__/ChatConversation.spec.tsx @@ -4,7 +4,7 @@ import React from 'react' import ChatMessageModel from 'shared/api/models/ChatMessageModel' import { renderWithRouterAndTheme } from '../../testing/render' -import ChatConversation, { TYPING_INDICATOR_TIMEOUT } from '../ChatConversation' +import ChatConversation from '../ChatConversation' jest.mock('react-i18next') window.HTMLElement.prototype.scrollIntoView = jest.fn() @@ -61,18 +61,14 @@ describe('ChatConversation', () => { expect(getByTestId(testMessages[1]!.id)).toBeTruthy() expect(getByText('...')).toBeTruthy() - act(() => { - jest.advanceTimersByTime(TYPING_INDICATOR_TIMEOUT) - }) + act(() => jest.runAllTimers()) expect(queryByText('...')).toBeNull() }) it('should display typing indicator after opening the chatbot with existing conversation for unanswered user message', () => { const { queryByText, getByTestId } = render(testMessages, false) expect(getByTestId(testMessages[3]!.id)).toBeTruthy() - act(() => { - jest.advanceTimersByTime(TYPING_INDICATOR_TIMEOUT) - }) + act(() => jest.runAllTimers()) expect(queryByText('...')).toBeNull() })