test(friends): update tests for unified friend search UI
Replace old "Add Friend" form tests (placeholder/button/inline messages) with new tests for the Find People panel: search input, Direct Add row, toast feedback, and searchUsers debounce. Also fix ancillary mocks (discoverStore, authStore, activityStore, ConnectInstanceModal, findExistingDmForUser) required by the rewritten component.
This commit is contained in:
@@ -5,6 +5,7 @@ import { MemoryRouter } from 'react-router-dom';
|
|||||||
import { FriendsPage } from './FriendsPage';
|
import { FriendsPage } from './FriendsPage';
|
||||||
import { useSocialStore, type TaggedFriend, type TaggedFriendRequest } from '../../stores/socialStore';
|
import { useSocialStore, type TaggedFriend, type TaggedFriendRequest } from '../../stores/socialStore';
|
||||||
import { useSpaceStore } from '../../stores/spaceStore';
|
import { useSpaceStore } from '../../stores/spaceStore';
|
||||||
|
import { useUIStore } from '../../stores/uiStore';
|
||||||
import type { Friend, FriendRequest } from '@backspace/shared';
|
import type { Friend, FriendRequest } from '@backspace/shared';
|
||||||
|
|
||||||
// Mock the mascot animation hook
|
// Mock the mascot animation hook
|
||||||
@@ -36,17 +37,80 @@ vi.mock('../../api/client', () => ({
|
|||||||
cancelRequest: vi.fn().mockResolvedValue({ success: true }),
|
cancelRequest: vi.fn().mockResolvedValue({ success: true }),
|
||||||
removeFriend: vi.fn().mockResolvedValue({ success: true }),
|
removeFriend: vi.fn().mockResolvedValue({ success: true }),
|
||||||
search: vi.fn().mockResolvedValue([]),
|
search: vi.fn().mockResolvedValue([]),
|
||||||
|
discover: vi.fn().mockResolvedValue({ users: [], total: 0 }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Mock the instanceStore (imported by socialStore)
|
// Mock the instanceStore (imported by socialStore)
|
||||||
vi.mock('../../stores/instanceStore', () => ({
|
vi.mock('../../stores/instanceStore', () => ({
|
||||||
useInstanceStore: {
|
useInstanceStore: Object.assign(
|
||||||
getState: () => ({ instances: [] }),
|
(selector: (s: any) => any) => selector({
|
||||||
setState: vi.fn(),
|
instances: [],
|
||||||
subscribe: vi.fn(),
|
_autoConnectDone: true,
|
||||||
},
|
}),
|
||||||
|
{
|
||||||
|
getState: () => ({ instances: [], _autoConnectDone: true }),
|
||||||
|
setState: vi.fn(),
|
||||||
|
subscribe: vi.fn(),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../stores/discoverStore', () => ({
|
||||||
|
useDiscoverStore: Object.assign(
|
||||||
|
(selector: (s: any) => any) => selector({
|
||||||
|
users: [],
|
||||||
|
isLoading: false,
|
||||||
|
searchQuery: '',
|
||||||
|
setSearchQuery: vi.fn(),
|
||||||
|
fetchUsers: vi.fn(),
|
||||||
|
updateRelationship: vi.fn(),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
getState: () => ({
|
||||||
|
users: [],
|
||||||
|
isLoading: false,
|
||||||
|
searchQuery: '',
|
||||||
|
fetchUsers: vi.fn(),
|
||||||
|
updateRelationship: vi.fn(),
|
||||||
|
}),
|
||||||
|
setState: vi.fn(),
|
||||||
|
subscribe: vi.fn(),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../stores/authStore', () => ({
|
||||||
|
useAuthStore: Object.assign(
|
||||||
|
(selector: (s: any) => any) => selector({
|
||||||
|
user: { id: 'current-user' },
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
getState: () => ({ user: { id: 'current-user' } }),
|
||||||
|
setState: vi.fn(),
|
||||||
|
subscribe: vi.fn(),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock activityStore
|
||||||
|
vi.mock('../../stores/activityStore', () => ({
|
||||||
|
useActivityStore: Object.assign(
|
||||||
|
(selector: (s: any) => any) => selector({
|
||||||
|
userActivities: new Map(),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
getState: () => ({ userActivities: new Map(), reset: vi.fn() }),
|
||||||
|
setState: vi.fn(),
|
||||||
|
subscribe: vi.fn(),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock ConnectInstanceModal
|
||||||
|
vi.mock('../modals/ConnectInstanceModal', () => ({
|
||||||
|
ConnectInstanceModal: () => null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const mockNavigate = vi.fn();
|
const mockNavigate = vi.fn();
|
||||||
@@ -122,72 +186,103 @@ beforeEach(() => {
|
|||||||
error: null,
|
error: null,
|
||||||
loadFriends: vi.fn(),
|
loadFriends: vi.fn(),
|
||||||
loadRequests: vi.fn(),
|
loadRequests: vi.fn(),
|
||||||
|
searchUsers: vi.fn().mockResolvedValue([]),
|
||||||
});
|
});
|
||||||
useSpaceStore.setState({
|
useSpaceStore.setState({
|
||||||
dmChannels: [],
|
dmChannels: [],
|
||||||
|
findExistingDmForUser: () => null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('FriendsPage', () => {
|
describe('FriendsPage', () => {
|
||||||
describe('Add Friend tab', () => {
|
describe('Add Friend tab', () => {
|
||||||
it('renders the Add Friend form when tab is clicked', async () => {
|
it('renders the search input when Add Friend tab is clicked', async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
renderFriendsPage();
|
renderFriendsPage();
|
||||||
|
|
||||||
const addFriendTab = screen.getByText('Add Friend');
|
const addFriendTab = screen.getByText('Add Friend');
|
||||||
await user.click(addFriendTab);
|
await user.click(addFriendTab);
|
||||||
|
|
||||||
expect(screen.getByPlaceholderText('You can add a friend with their username')).toBeInTheDocument();
|
expect(screen.getByPlaceholderText(/Search or add by username/)).toBeInTheDocument();
|
||||||
expect(screen.getByText('Send Friend Request')).toBeInTheDocument();
|
expect(screen.getByText('Find People')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls sendFriendRequest with the username when form is submitted', async () => {
|
it('shows Direct Add row and sends request for user@domain input', async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const mockSendFriendRequest = vi.fn().mockResolvedValue(undefined);
|
const mockSendFriendRequest = vi.fn().mockResolvedValue('req-123');
|
||||||
useSocialStore.setState({
|
useSocialStore.setState({
|
||||||
sendFriendRequest: mockSendFriendRequest,
|
sendFriendRequest: mockSendFriendRequest,
|
||||||
});
|
});
|
||||||
|
|
||||||
renderFriendsPage();
|
renderFriendsPage();
|
||||||
|
|
||||||
// Switch to Add Friend tab
|
|
||||||
await user.click(screen.getByText('Add Friend'));
|
await user.click(screen.getByText('Add Friend'));
|
||||||
|
|
||||||
// Type username
|
const input = screen.getByPlaceholderText(/Search or add by username/);
|
||||||
const input = screen.getByPlaceholderText('You can add a friend with their username');
|
await user.type(input, 'newbuddy@remote.example.com');
|
||||||
await user.type(input, 'newbuddy');
|
|
||||||
|
|
||||||
// Click send
|
// Direct Add row should appear
|
||||||
await user.click(screen.getByText('Send Friend Request'));
|
expect(screen.getByText(/Send friend request to/)).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Click Send Request
|
||||||
|
await user.click(screen.getByText('Send Request'));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockSendFriendRequest).toHaveBeenCalledWith('newbuddy');
|
expect(mockSendFriendRequest).toHaveBeenCalledWith('newbuddy@remote.example.com');
|
||||||
});
|
|
||||||
|
|
||||||
// Should show success message
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByText(/Success! Your friend request to newbuddy has been sent/)).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows error when sendFriendRequest fails', async () => {
|
it('shows toast when Direct Add request fails', async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const mockSendFriendRequest = vi.fn().mockRejectedValue(new Error('User not found'));
|
const mockSendFriendRequest = vi.fn().mockRejectedValue(new Error('User not found'));
|
||||||
|
const mockAddToast = vi.fn();
|
||||||
useSocialStore.setState({
|
useSocialStore.setState({
|
||||||
sendFriendRequest: mockSendFriendRequest,
|
sendFriendRequest: mockSendFriendRequest,
|
||||||
});
|
});
|
||||||
|
useUIStore.setState({
|
||||||
|
addToast: mockAddToast,
|
||||||
|
});
|
||||||
|
|
||||||
renderFriendsPage();
|
renderFriendsPage();
|
||||||
await user.click(screen.getByText('Add Friend'));
|
await user.click(screen.getByText('Add Friend'));
|
||||||
|
|
||||||
const input = screen.getByPlaceholderText('You can add a friend with their username');
|
const input = screen.getByPlaceholderText(/Search or add by username/);
|
||||||
await user.type(input, 'ghost');
|
await user.type(input, 'ghost@remote.example.com');
|
||||||
await user.click(screen.getByText('Send Friend Request'));
|
await user.click(screen.getByText('Send Request'));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByText('User not found')).toBeInTheDocument();
|
expect(mockAddToast).toHaveBeenCalledWith('User not found', 'warning');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not show Direct Add row for plain usernames', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
renderFriendsPage();
|
||||||
|
await user.click(screen.getByText('Add Friend'));
|
||||||
|
|
||||||
|
const input = screen.getByPlaceholderText(/Search or add by username/);
|
||||||
|
await user.type(input, 'marc');
|
||||||
|
|
||||||
|
expect(screen.queryByText(/Send friend request to/)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls searchUsers when typing a non-@ query', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const mockSearchUsers = vi.fn().mockResolvedValue([]);
|
||||||
|
useSocialStore.setState({
|
||||||
|
searchUsers: mockSearchUsers,
|
||||||
|
});
|
||||||
|
|
||||||
|
renderFriendsPage();
|
||||||
|
await user.click(screen.getByText('Add Friend'));
|
||||||
|
|
||||||
|
const input = screen.getByPlaceholderText(/Search or add by username/);
|
||||||
|
await user.type(input, 'marc');
|
||||||
|
|
||||||
|
// Wait for debounce
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockSearchUsers).toHaveBeenCalledWith('marc');
|
||||||
|
}, { timeout: 500 });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('DM button on friend item', () => {
|
describe('DM button on friend item', () => {
|
||||||
@@ -202,6 +297,7 @@ describe('FriendsPage', () => {
|
|||||||
});
|
});
|
||||||
useSpaceStore.setState({
|
useSpaceStore.setState({
|
||||||
addDmChannel: mockAddDmChannel,
|
addDmChannel: mockAddDmChannel,
|
||||||
|
findExistingDmForUser: () => null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mock the dm.create API
|
// Mock the dm.create API
|
||||||
@@ -226,7 +322,7 @@ describe('FriendsPage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockAddDmChannel).toHaveBeenCalledWith(expect.objectContaining({ id: 'dm-channel-99' }));
|
expect(mockAddDmChannel).toHaveBeenCalledWith(expect.objectContaining({ id: 'dm-channel-99' }), '');
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user