Fix sidebar and test typing

This commit is contained in:
Jack Kingsman
2026-02-14 18:04:17 -08:00
parent 36098f62b8
commit c91449260d
2 changed files with 57 additions and 58 deletions

View File

@@ -258,61 +258,60 @@ export function Sidebar({
setContactsCollapsed(prev.contacts);
setRepeatersCollapsed(prev.repeaters);
}
}, [
isSearching,
favoritesCollapsed,
channelsCollapsed,
contactsCollapsed,
repeatersCollapsed,
]);
}, [isSearching, favoritesCollapsed, channelsCollapsed, contactsCollapsed, repeatersCollapsed]);
// Separate favorites from regular items, and build combined favorites list
const { favoriteItems, nonFavoriteChannels, nonFavoriteContacts, nonFavoriteRepeaters } = useMemo(() => {
const favChannels = filteredChannels.filter((c) => isFavorite(favorites, 'channel', c.key));
const favContacts = [...filteredNonRepeaterContacts, ...filteredRepeaters].filter((c) =>
isFavorite(favorites, 'contact', c.public_key)
);
const nonFavChannels = filteredChannels.filter((c) => !isFavorite(favorites, 'channel', c.key));
const nonFavContacts = filteredNonRepeaterContacts.filter(
(c) => !isFavorite(favorites, 'contact', c.public_key)
);
const nonFavRepeaters = filteredRepeaters.filter(
(c) => !isFavorite(favorites, 'contact', c.public_key)
);
const { favoriteItems, nonFavoriteChannels, nonFavoriteContacts, nonFavoriteRepeaters } =
useMemo(() => {
const favChannels = filteredChannels.filter((c) => isFavorite(favorites, 'channel', c.key));
const favContacts = [...filteredNonRepeaterContacts, ...filteredRepeaters].filter((c) =>
isFavorite(favorites, 'contact', c.public_key)
);
const nonFavChannels = filteredChannels.filter(
(c) => !isFavorite(favorites, 'channel', c.key)
);
const nonFavContacts = filteredNonRepeaterContacts.filter(
(c) => !isFavorite(favorites, 'contact', c.public_key)
);
const nonFavRepeaters = filteredRepeaters.filter(
(c) => !isFavorite(favorites, 'contact', c.public_key)
);
const items: FavoriteItem[] = [
...favChannels.map((channel) => ({ type: 'channel' as const, channel })),
...favContacts.map((contact) => ({ type: 'contact' as const, contact })),
].sort((a, b) => {
const timeA =
a.type === 'channel'
? getLastMessageTime('channel', a.channel.key)
: getLastMessageTime('contact', a.contact.public_key);
const timeB =
b.type === 'channel'
? getLastMessageTime('channel', b.channel.key)
: getLastMessageTime('contact', b.contact.public_key);
if (timeA && timeB) return timeB - timeA;
if (timeA && !timeB) return -1;
if (!timeA && timeB) return 1;
const nameA = a.type === 'channel' ? a.channel.name : a.contact.name || a.contact.public_key;
const nameB = b.type === 'channel' ? b.channel.name : b.contact.name || b.contact.public_key;
return nameA.localeCompare(nameB);
});
const items: FavoriteItem[] = [
...favChannels.map((channel) => ({ type: 'channel' as const, channel })),
...favContacts.map((contact) => ({ type: 'contact' as const, contact })),
].sort((a, b) => {
const timeA =
a.type === 'channel'
? getLastMessageTime('channel', a.channel.key)
: getLastMessageTime('contact', a.contact.public_key);
const timeB =
b.type === 'channel'
? getLastMessageTime('channel', b.channel.key)
: getLastMessageTime('contact', b.contact.public_key);
if (timeA && timeB) return timeB - timeA;
if (timeA && !timeB) return -1;
if (!timeA && timeB) return 1;
const nameA =
a.type === 'channel' ? a.channel.name : a.contact.name || a.contact.public_key;
const nameB =
b.type === 'channel' ? b.channel.name : b.contact.name || b.contact.public_key;
return nameA.localeCompare(nameB);
});
return {
favoriteItems: items,
nonFavoriteChannels: nonFavChannels,
nonFavoriteContacts: nonFavContacts,
nonFavoriteRepeaters: nonFavRepeaters,
};
}, [
filteredChannels,
filteredNonRepeaterContacts,
filteredRepeaters,
favorites,
getLastMessageTime,
]);
return {
favoriteItems: items,
nonFavoriteChannels: nonFavChannels,
nonFavoriteContacts: nonFavContacts,
nonFavoriteRepeaters: nonFavRepeaters,
};
}, [
filteredChannels,
filteredNonRepeaterContacts,
filteredRepeaters,
favorites,
getLastMessageTime,
]);
const buildChannelRow = (channel: Channel, keyPrefix: string): ConversationRow => ({
key: `${keyPrefix}-${channel.key}`,

View File

@@ -38,10 +38,11 @@ function renderSidebar(overrides?: {
favorites?: Favorite[];
lastMessageTimes?: ConversationTimes;
}) {
const aliceName = 'Alice';
const publicChannel = makeChannel('AA'.repeat(16), 'Public');
const flightChannel = makeChannel('BB'.repeat(16), '#flight');
const opsChannel = makeChannel('CC'.repeat(16), '#ops');
const alice = makeContact('11'.repeat(32), 'Alice');
const alice = makeContact('11'.repeat(32), aliceName);
const relay = makeContact('22'.repeat(32), 'Relay', CONTACT_TYPE_REPEATER);
const unreadCounts = overrides?.unreadCounts ?? {
@@ -73,7 +74,7 @@ function renderSidebar(overrides?: {
/>
);
return { flightChannel, opsChannel, alice };
return { flightChannel, opsChannel, aliceName };
}
function getSectionHeaderContainer(title: string): HTMLElement {
@@ -94,27 +95,26 @@ describe('Sidebar section summaries', () => {
});
it('expands collapsed sections during search and restores collapse state after clearing search', async () => {
const { opsChannel, alice } = renderSidebar();
const { opsChannel, aliceName } = renderSidebar();
fireEvent.click(screen.getByRole('button', { name: /Channels/i }));
fireEvent.click(screen.getByRole('button', { name: /Contacts/i }));
expect(screen.queryByText(opsChannel.name)).not.toBeInTheDocument();
expect(screen.queryByText(alice.name)).not.toBeInTheDocument();
expect(screen.queryByText(aliceName)).not.toBeInTheDocument();
const search = screen.getByPlaceholderText('Search...');
fireEvent.change(search, { target: { value: 'alice' } });
await waitFor(() => {
expect(screen.getByText(alice.name)).toBeInTheDocument();
expect(screen.getByText(aliceName)).toBeInTheDocument();
});
fireEvent.change(search, { target: { value: '' } });
await waitFor(() => {
expect(screen.queryByText(opsChannel.name)).not.toBeInTheDocument();
expect(screen.queryByText(alice.name)).not.toBeInTheDocument();
expect(screen.queryByText(aliceName)).not.toBeInTheDocument();
});
});
});