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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed
- Silenced a false-positive `MaxListenersExceededWarning` logged on every request proxied through an external rewrite. [#1697](https://github.com/sourcebot-dev/sourcebot/pull/1697)
- Fixed selected language and repository filters disappearing when filtering the available options. [#1685](https://github.com/sourcebot-dev/sourcebot/pull/1685)

## [5.1.15] - 2026-09-29

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { TooltipProvider } from '@/components/ui/tooltip';
import { Filter } from './filter';
import type { Entry } from './entry';

afterEach(cleanup);

const createEntry = (displayName: string, isSelected = false, count = 1): Entry => ({
key: displayName,
displayName,
count,
isSelected,
isHidden: false,
isDisabled: false,
});

const renderFilter = (entries: Entry[], onEntryClicked = vi.fn()) => render(
<TooltipProvider>
<Filter
title="Languages"
searchPlaceholder="Filter languages"
entries={entries}
onEntryClicked={onEntryClicked}
isStreaming={false}
/>
</TooltipProvider>,
);

describe('search filters', () => {
it('keeps selected languages above matches and allows clearing a hidden-by-search selection', () => {
const onEntryClicked = vi.fn();
renderFilter([
createEntry('C', false, 100),
createEntry('PowerShell', true),
createEntry('Python'),
], onEntryClicked);

fireEvent.change(screen.getByPlaceholderText('Filter languages'), { target: { value: 'C' } });

const selected = screen.getByText('PowerShell');
const match = screen.getByText('C');
expect(selected.compareDocumentPosition(match) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
expect(screen.queryByText('Python')).toBeNull();

fireEvent.click(selected);
expect(onEntryClicked).toHaveBeenCalledExactlyOnceWith('PowerShell');
});

it('shows a selected entry once when it also matches the filter', () => {
renderFilter([createEntry('Python', true), createEntry('PowerShell')]);

fireEvent.change(screen.getByPlaceholderText('Filter languages'), { target: { value: 'Python' } });

expect(screen.getAllByText('Python')).toHaveLength(1);
expect(screen.queryByText('PowerShell')).toBeNull();
});

it('preserves selected entries when the filter has no matches', () => {
renderFilter([createEntry('PowerShell', true), createEntry('Python')]);

fireEvent.change(screen.getByPlaceholderText('Filter languages'), { target: { value: 'zzzz' } });

expect(screen.getByText('PowerShell')).toBeTruthy();
expect(screen.queryByText('Python')).toBeNull();
});

it('does not reorder the caller\'s entries when sorting the unfiltered list', () => {
const entries = [createEntry('Python'), createEntry('PowerShell', true)];
Object.freeze(entries);

renderFilter(entries);

expect(entries.map(entry => entry.key)).toEqual(['Python', 'PowerShell']);
expect(screen.getByText('PowerShell')).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,14 @@ export const Filter = ({
return entries;
}

const fuse = new Fuse(entries, {
const selectedEntries = entries.filter(entry => entry.isSelected);
const fuse = new Fuse(entries.filter(entry => !entry.isSelected), {
keys: ["displayName"],
threshold: 0.3,
});

const result = fuse.search(searchFilter);
return result.map((result) => result.item);
return [...selectedEntries, ...result.map((result) => result.item)];
}, [entries, searchFilter]);

return (
Expand All @@ -61,7 +62,7 @@ export const Filter = ({
<div
className="flex flex-col gap-0.5 text-sm overflow-scroll no-scrollbar"
>
{filteredEntries
{[...filteredEntries]
.sort((entryA, entryB) => compareEntries(entryB, entryA))
.map((entry) => (
<Entry
Expand Down