MM-67904 Fix inflated count in search results Messages tab (#36465)

* MM-67904 Fix inflated count in search results Messages tab

The "Messages" counter in the search results RHS was rendering
`results.length`, but `results` is the array produced by
`makeAddDateSeparatorsForSearchResults`, which interleaves
date-line strings between posts (one per date group). A search
returning a single post therefore showed "2", and N posts spread
across D dates showed N+D.

Filter date-line strings out of `results` before computing the
counter so it reflects only the actual matching posts. File
results are unaffected.

Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>

* Address review feedback: simplify count predicate, drop unnecessary type casts in tests

- Count non-string entries in results directly. The Props type already
  declares results as Array<Post | string>, so any string entry is a
  separator; this is clearer than checking isDateLine and is more
  defensive against future marker strings.
- Drop the as any casts on the new test results props. The literals are
  assignable to Array<Post | string>, so the casts only suppressed
  type checking.

Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>
This commit is contained in:
Miguel de la Cruz
2026-05-07 19:47:19 +02:00
committed by GitHub
parent 69fbaeced9
commit 81f3af4738
2 changed files with 63 additions and 1 deletions
@@ -4,6 +4,8 @@
import {within} from '@testing-library/react';
import React from 'react';
import {DATE_LINE} from 'mattermost-redux/utils/post_list';
import SearchResults, {arePropsEqual} from 'components/search_results/search_results';
import {renderWithContext} from 'tests/react_testing_utils';
@@ -168,6 +170,59 @@ describe('components/SearchResults', () => {
});
});
describe('messagesCounter', () => {
// Render with the Files tab selected so the body of the panel does not
// try to render the message Post items (whose connected components
// require Redux state that is out of scope for these tests). The
// messagesCounter is rendered on the Messages tab regardless of the
// active tab, so this still exercises the count calculation.
test('should not count date separators in the messages counter', () => {
const post = TestHelper.getPostMock({id: 'post1', message: 'unique message'});
const dateLine = DATE_LINE + new Date('2026-03-12').getTime();
const {container} = renderSearchResults({
results: [dateLine, post],
searchType: 'files',
isSearchAtEnd: true,
});
const counter = container.querySelector('.messages-tab .counter');
expect(counter).not.toBeNull();
expect(counter?.textContent).toBe('1');
});
test('should count only posts when multiple date separators are present', () => {
const post1 = TestHelper.getPostMock({id: 'post1', message: 'unique message 1'});
const post2 = TestHelper.getPostMock({id: 'post2', message: 'unique message 2'});
const dateLine1 = DATE_LINE + new Date('2026-03-12').getTime();
const dateLine2 = DATE_LINE + new Date('2026-03-13').getTime();
const {container} = renderSearchResults({
results: [dateLine1, post1, dateLine2, post2],
searchType: 'files',
isSearchAtEnd: true,
});
const counter = container.querySelector('.messages-tab .counter');
expect(counter?.textContent).toBe('2');
});
test('should append "+" to the messages counter when more results may be available', () => {
const post = TestHelper.getPostMock({id: 'post1', message: 'unique message'});
const dateLine = DATE_LINE + new Date('2026-03-12').getTime();
const {container} = renderSearchResults({
results: [dateLine, post],
searchType: 'files',
isSearchAtEnd: false,
searchPage: 1,
});
const counter = container.querySelector('.messages-tab .counter');
expect(counter?.textContent).toBe('1+');
});
});
describe('arePropsEqual', () => {
const result1 = {test: 'test'};
const result2 = {test: 'test'};
@@ -150,6 +150,13 @@ const SearchResults: React.FC<Props> = (props: Props): JSX.Element => {
const noResults = (!results || !Array.isArray(results) || results.length === 0);
const noFileResults = (!fileResults || !Array.isArray(fileResults) || fileResults.length === 0);
// The `results` prop is typed as `Array<Post | string>`. Strings are
// non-Post entries (date separators injected by
// makeAddDateSeparatorsForSearchResults), so the messages counter only
// counts the non-string entries. Otherwise the count is inflated by one
// per date group (see MM-67904).
const messagesCount = Array.isArray(results) ? results.filter((item) => typeof item !== 'string').length : 0;
const isLoading = isSearchingTerm || isSearchingFlaggedPost || isSearchingPinnedPost || !isOpened;
const isAtEnd = (searchType === DataSearchTypes.MESSAGES_SEARCH_TYPE && isSearchAtEnd) || (searchType === DataSearchTypes.FILES_SEARCH_TYPE && isSearchFilesAtEnd);
const showLoadMore = !isAtEnd && !isChannelFiles && !isFlaggedPosts && !isPinnedPosts;
@@ -390,7 +397,7 @@ const SearchResults: React.FC<Props> = (props: Props): JSX.Element => {
selected={searchType}
selectedFilter={searchFilterType}
isFileAttachmentsEnabled={isFileAttachmentsEnabled(config)}
messagesCounter={isSearchAtEnd || props.searchPage === 0 ? `${results.length}` : `${results.length}+`}
messagesCounter={isSearchAtEnd || props.searchPage === 0 ? `${messagesCount}` : `${messagesCount}+`}
filesCounter={isSearchFilesAtEnd || props.searchPage === 0 ? `${fileResults.length}` : `${fileResults.length}+`}
onChange={setSearchType}
onFilter={setSearchFilterType}