feat: replace Wumpus empty states with Nori mascot on Friends page
This commit is contained in:
@@ -7,6 +7,21 @@ import { useSocialStore, type TaggedFriend, type TaggedFriendRequest } from '../
|
|||||||
import { useSpaceStore } from '../../stores/spaceStore';
|
import { useSpaceStore } from '../../stores/spaceStore';
|
||||||
import type { Friend, FriendRequest } from '@backspace/shared';
|
import type { Friend, FriendRequest } from '@backspace/shared';
|
||||||
|
|
||||||
|
// Mock the mascot animation hook
|
||||||
|
vi.mock('../../hooks/useMascotAnimation', () => ({
|
||||||
|
useMascotAnimation: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Stub AudioManager to avoid AudioWorkletNode reference error in jsdom
|
||||||
|
vi.mock('../../audio/AudioManager', () => ({
|
||||||
|
AudioManager: {
|
||||||
|
getInstance: vi.fn().mockReturnValue({
|
||||||
|
setOutputDevice: vi.fn(),
|
||||||
|
setVolume: vi.fn(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
// Mock the api module
|
// Mock the api module
|
||||||
vi.mock('../../api/client', () => ({
|
vi.mock('../../api/client', () => ({
|
||||||
api: {
|
api: {
|
||||||
@@ -365,4 +380,36 @@ describe('FriendsPage', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Empty state mascot messages', () => {
|
||||||
|
it('shows "No one\'s online right now." in online tab when no friends online', () => {
|
||||||
|
useSocialStore.setState({ friends: [], requests: [] });
|
||||||
|
renderFriendsPage();
|
||||||
|
|
||||||
|
expect(screen.getByText("No one's online right now.")).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/Wumpus/)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows "No friends yet — add someone!" in all tab when no friends', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
useSocialStore.setState({ friends: [], requests: [] });
|
||||||
|
renderFriendsPage();
|
||||||
|
|
||||||
|
await user.click(screen.getByText('All'));
|
||||||
|
|
||||||
|
expect(screen.getByText('No friends yet — add someone!')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/Wumpus/)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows "No pending requests — Nori is napping." in pending tab when empty', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
useSocialStore.setState({ friends: [], requests: [] });
|
||||||
|
renderFriendsPage();
|
||||||
|
|
||||||
|
await user.click(screen.getByText('Pending'));
|
||||||
|
|
||||||
|
expect(screen.getByText('No pending requests — Nori is napping.')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/Wumpus/)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { MemberListToggleButton } from '../layout/MemberListToggleButton';
|
|||||||
import { LoadingSpinner } from '../ui/LoadingSpinner';
|
import { LoadingSpinner } from '../ui/LoadingSpinner';
|
||||||
import { getAvatarGradient } from '../../utils/gradients';
|
import { getAvatarGradient } from '../../utils/gradients';
|
||||||
import { api } from '../../api/client';
|
import { api } from '../../api/client';
|
||||||
|
import { Mascot } from '../ui/Mascot';
|
||||||
|
|
||||||
type Tab = 'online' | 'all' | 'pending' | 'add';
|
type Tab = 'online' | 'all' | 'pending' | 'add';
|
||||||
|
|
||||||
@@ -97,9 +98,9 @@ export function FriendsPage({ mobile }: FriendsPageProps) {
|
|||||||
Online — {onlineFriends.length}
|
Online — {onlineFriends.length}
|
||||||
</h2>
|
</h2>
|
||||||
{onlineFriends.length === 0 ? (
|
{onlineFriends.length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center h-full opacity-60">
|
<div className="flex flex-col items-center justify-center h-full opacity-80">
|
||||||
<img src="/friends-empty.svg" alt="" className="w-64 h-64 mb-4" onError={(e) => (e.target as any).style.display='none'} />
|
<Mascot state="idle" className="w-32 h-32 mb-4" />
|
||||||
<p className="text-txt-tertiary">No one's around to play with Wumpus.</p>
|
<p className="text-txt-tertiary text-sm">No one's online right now.</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
onlineFriends.map(friend => (
|
onlineFriends.map(friend => (
|
||||||
@@ -115,8 +116,9 @@ export function FriendsPage({ mobile }: FriendsPageProps) {
|
|||||||
All Friends — {friends.length}
|
All Friends — {friends.length}
|
||||||
</h2>
|
</h2>
|
||||||
{friends.length === 0 ? (
|
{friends.length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center h-full opacity-60">
|
<div className="flex flex-col items-center justify-center h-full opacity-80">
|
||||||
<p className="text-txt-tertiary">Wumpus is waiting on friends. You can add them!</p>
|
<Mascot state="lonely" className="w-32 h-32 mb-4" />
|
||||||
|
<p className="text-txt-tertiary text-sm">No friends yet — add someone!</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
friends.map(friend => (
|
friends.map(friend => (
|
||||||
@@ -132,8 +134,9 @@ export function FriendsPage({ mobile }: FriendsPageProps) {
|
|||||||
Pending — {pendingIncoming.length + pendingOutgoing.length}
|
Pending — {pendingIncoming.length + pendingOutgoing.length}
|
||||||
</h2>
|
</h2>
|
||||||
{[...pendingIncoming, ...pendingOutgoing].length === 0 ? (
|
{[...pendingIncoming, ...pendingOutgoing].length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center h-full opacity-60">
|
<div className="flex flex-col items-center justify-center h-full opacity-80">
|
||||||
<p className="text-txt-tertiary">There are no pending friend requests. Here's Wumpus for now!</p>
|
<Mascot state="sleeping" className="w-32 h-32 mb-4" />
|
||||||
|
<p className="text-txt-tertiary text-sm">No pending requests — Nori is napping.</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|||||||
Reference in New Issue
Block a user