From f6e8ebc2e95b00d951a296b2c68e44c3cb116964 Mon Sep 17 00:00:00 2001 From: Rahim Rahman Date: Fri, 10 Oct 2025 10:51:56 -0600 Subject: [PATCH] fix(MM-64763): search page doesn't clear hashtag on search input (#9194) --- app/screens/home/search/search.test.tsx | 61 +++++++++++++++++++++++++ app/screens/home/search/search.tsx | 6 ++- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/app/screens/home/search/search.test.tsx b/app/screens/home/search/search.test.tsx index bb9f0c344..f0f493ad3 100644 --- a/app/screens/home/search/search.test.tsx +++ b/app/screens/home/search/search.test.tsx @@ -204,4 +204,65 @@ describe('SearchScreen', () => { expect.any(String), ); }); + + it('does not restore searchTerm after clearing when navigated with hashtag', async () => { + const mockNavigation = { + getState: () => ({ + index: 0, + routes: [{params: {searchTerm: '#hashtag'}}], + }), + }; + + jest.spyOn(require('@react-navigation/native'), 'useNavigation').mockReturnValue(mockNavigation); + + const {getByTestId} = renderWithEverything( + , + {database}, + ); + + await waitFor(() => { + const searchInput = getByTestId('navigation.header.search_bar.search.input'); + expect(searchInput.props.value).toBe('#hashtag'); + }); + + const searchInput = getByTestId('navigation.header.search_bar.search.input'); + const clearButton = getByTestId('navigation.header.search_bar.search.clear.button'); + + act(() => { + fireEvent.press(clearButton); + }); + + await waitFor(() => { + expect(searchInput.props.value).toBe(''); + }); + }); + + it('populates searchTerm when navigating to screen with hashtag', async () => { + const mockNavigation = { + getState: () => ({ + index: 0, + routes: [{params: {searchTerm: '#newtag'}}], + }), + }; + + jest.spyOn(require('@react-navigation/native'), 'useNavigation').mockReturnValue(mockNavigation); + + const {getByTestId} = renderWithEverything( + , + {database}, + ); + + await waitFor(() => { + const searchInput = getByTestId('navigation.header.search_bar.search.input'); + expect(searchInput.props.value).toBe('#newtag'); + }); + + await waitFor(() => { + expect(searchPosts).toHaveBeenCalledWith( + expect.any(String), + 'team1', + expect.objectContaining({terms: '#newtag'}), + ); + }); + }); }); diff --git a/app/screens/home/search/search.tsx b/app/screens/home/search/search.tsx index b5e9b97d6..0db55e6e7 100644 --- a/app/screens/home/search/search.tsx +++ b/app/screens/home/search/search.tsx @@ -112,6 +112,7 @@ const SearchScreen = ({teamId, teams, crossTeamSearchEnabled}: Props) => { const clearRef = useRef(false); const cancelRef = useRef(false); const searchRef = useRef(null); + const processedSearchTermRef = useRef(''); const [cursorPosition, setCursorPosition] = useState(searchTerm?.length || 0); const [searchValue, setSearchValue] = useState(searchTerm || ''); @@ -137,6 +138,7 @@ const SearchScreen = ({teamId, teams, crossTeamSearchEnabled}: Props) => { const onSnap = useCallback((offset: number, animated = true) => { scrollRef.current?.scrollToOffset({offset, animated}); + // eslint-disable-next-line react-hooks/exhaustive-deps -- scrollRef is a ref object, so its reference should not change between renders }, []); const onSnapWithTimeout = useCallback((offset: number, animated = true) => { @@ -365,7 +367,8 @@ const SearchScreen = ({teamId, teams, crossTeamSearchEnabled}: Props) => { }, [unlock, onSnapWithTimeout]); useEffect(() => { - if (searchTerm) { + if (searchTerm && searchTerm !== processedSearchTermRef.current) { + processedSearchTermRef.current = searchTerm; clearInputs(); setSearchValue(searchTerm); handleSearch(searchTeamId, searchTerm); @@ -379,6 +382,7 @@ const SearchScreen = ({teamId, teams, crossTeamSearchEnabled}: Props) => { }, 300); } else { setAutoScroll(false); + processedSearchTermRef.current = ''; } }, [isFocused]);