Files
Toju/toju-app/src/app/features/dashboard/dashboard.component.spec.ts
2026-06-05 01:51:03 +02:00

244 lines
8.2 KiB
TypeScript

import '@angular/compiler';
import {
describe,
it,
expect,
vi
} from 'vitest';
import {
Injector,
runInInjectionContext,
signal
} from '@angular/core';
import { Router } from '@angular/router';
import { Store } from '@ngrx/store';
import { of } from 'rxjs';
import { DashboardComponent } from './dashboard.component';
import { ViewportService } from '../../core/platform';
import { ServerDirectoryFacade } from '../../domains/server-directory/application/facades/server-directory.facade';
import { FriendService } from '../../domains/direct-message/application/services/friend.service';
import { RoomsActions } from '../../store/rooms/rooms.actions';
import {
selectSearchResults,
selectIsSearching,
selectSavedRooms
} from '../../store/rooms/rooms.selectors';
import { selectAllUsers, selectCurrentUser } from '../../store/users/users.selectors';
import type { ServerInfo } from '../../domains/server-directory/domain/models/server-directory.model';
import type { Room, User } from '../../shared-kernel';
interface HarnessOptions {
searchResults?: ServerInfo[];
saved?: Room[];
users?: User[];
currentUser?: User | null;
featured?: ServerInfo[];
trending?: ServerInfo[];
friendIds?: string[];
isMobile?: boolean;
}
function makeServer(id: string, name = id): ServerInfo {
return { id, name, maxUsers: 50, userCount: 1, isPrivate: false } as unknown as ServerInfo;
}
function createHarness(options: HarnessOptions = {}) {
const dispatch = vi.fn();
const searchResultsSig = signal<ServerInfo[]>(options.searchResults ?? []);
const savedSig = signal<Room[]>(options.saved ?? []);
const usersSig = signal<User[]>(options.users ?? []);
const currentUserSig = signal<User | null>(options.currentUser ?? null);
const store = {
selectSignal: (selector: unknown) => {
if (selector === selectSearchResults) {
return searchResultsSig;
}
if (selector === selectIsSearching) {
return signal(false);
}
if (selector === selectSavedRooms) {
return savedSig;
}
if (selector === selectAllUsers) {
return usersSig;
}
if (selector === selectCurrentUser) {
return currentUserSig;
}
return signal(null);
},
dispatch
} as unknown as Store;
const router = { navigate: vi.fn() } as unknown as Router;
const getFeaturedServers = vi.fn(() => of(options.featured ?? []));
const getTrendingServers = vi.fn(() => of(options.trending ?? []));
const serverDirectory = { getFeaturedServers, getTrendingServers } as unknown as ServerDirectoryFacade;
const friendIds = new Set<string>(options.friendIds ?? []);
const friendService = { friendIds: () => friendIds, friends: () => [] } as unknown as FriendService;
const injector = Injector.create({
providers: [
DashboardComponent,
{ provide: Store, useValue: store },
{ provide: Router, useValue: router },
{ provide: ServerDirectoryFacade, useValue: serverDirectory },
{ provide: FriendService, useValue: friendService },
{ provide: ViewportService, useValue: { isMobile: signal(options.isMobile ?? false) } }
]
});
const component = runInInjectionContext(injector, () => injector.get(DashboardComponent));
return {
component,
dispatch,
router,
getFeaturedServers,
getTrendingServers
};
}
describe('DashboardComponent', () => {
it('exposes the mobile viewport flag', () => {
expect(createHarness().component.isMobile()).toBe(false);
expect(createHarness({ isMobile: true }).component.isMobile()).toBe(true);
});
it('reports a new user when there are no servers or users', () => {
const { component } = createHarness();
expect(component.isNewUser()).toBe(true);
});
it('filters people by the active query', () => {
const { component } = createHarness({
users: [{ id: 'u1', displayName: 'Alice' } as unknown as User, { id: 'u2', displayName: 'Bob' } as unknown as User]
});
component.onSearchChange('ali');
expect(component.topPeopleResults().map((user) => user.id)).toEqual(['u1']);
});
it('limits server quick results', () => {
const { component } = createHarness({
searchResults: Array.from({ length: 8 }, (_, index) => makeServer(`s${index}`))
});
component.onSearchChange('s');
expect(component.topServerResults()).toHaveLength(5);
});
it('exposes an invite result for invite-like queries', () => {
const { component } = createHarness();
component.onSearchChange('https://app.test/invite/Code_42');
expect(component.inviteResult()).toBe('Code_42');
});
it('opens a joined server in place and routes others to the servers page', () => {
const joined = { id: 's1', name: 'Joined' } as unknown as Room;
const { component, dispatch, router } = createHarness({ saved: [joined] });
component.openServer(makeServer('s1', 'Joined'));
expect(dispatch).toHaveBeenCalledWith(RoomsActions.viewServer({ room: joined }));
component.openServer(makeServer('s2', 'Other'));
expect(router.navigate).toHaveBeenCalledWith(['/servers']);
});
it('navigates to the invite route when opening an invite', () => {
const { component, router } = createHarness();
component.onSearchChange('abc123');
component.openInvite();
expect(router.navigate).toHaveBeenCalledWith(['/invite', 'abc123']);
});
it('suggests people you might know independent of the query, excluding self', () => {
const { component } = createHarness({
users: [{ id: 'u1', oderId: 'u1', displayName: 'Alice' } as unknown as User, { id: 'u2', oderId: 'u2', displayName: 'Bob' } as unknown as User],
currentUser: { id: 'u1', oderId: 'u1' } as unknown as User
});
expect(component.peopleYouMightKnow().map((user) => user.id)).toEqual(['u2']);
});
it('loads popular servers from featured results on init', () => {
const featured = [makeServer('f1'), makeServer('f2')];
const { component } = createHarness({ featured });
component.ngOnInit();
expect(component.popularServers().map((server) => server.id)).toEqual(['f1', 'f2']);
});
it('falls back to trending servers when featured is empty', () => {
const trending = [makeServer('t1')];
const { component } = createHarness({ featured: [], trending });
component.ngOnInit();
expect(component.popularServers().map((server) => server.id)).toEqual(['t1']);
});
it('limits recently active servers to the discovery cap', () => {
const saved = Array.from({ length: 9 }, (_, index) => ({ id: `r${index}`, name: `Room ${index}`, userCount: 1 }) as unknown as Room);
const { component } = createHarness({ saved });
expect(component.recentlyActiveServers()).toHaveLength(5);
});
it('excludes existing friends from people you might know and lists them under friends', () => {
const { component } = createHarness({
users: [
{ id: 'u1', oderId: 'u1', displayName: 'Alice' } as unknown as User,
{ id: 'u2', oderId: 'u2', displayName: 'Bob' } as unknown as User,
{ id: 'u3', oderId: 'u3', displayName: 'Cara' } as unknown as User
],
currentUser: { id: 'u1', oderId: 'u1' } as unknown as User,
friendIds: ['u2']
});
expect(component.peopleYouMightKnow().map((user) => user.id)).toEqual(['u3']);
expect(component.friends().map((user) => user.id)).toEqual(['u2']);
});
it('records, removes, and clears recent searches', () => {
const { component } = createHarness();
component.onSearchChange('gaming');
component.submitSearch();
component.onSearchChange('music');
component.submitSearch();
expect(component.recentSearches()).toEqual(['music', 'gaming']);
component.removeRecentSearch('gaming');
expect(component.recentSearches()).toEqual(['music']);
component.clearRecentSearches();
expect(component.recentSearches()).toEqual([]);
});
it('deduplicates recent searches and keeps the most recent first', () => {
const { component } = createHarness();
component.onSearchChange('gaming');
component.submitSearch();
component.onSearchChange('music');
component.submitSearch();
component.onSearchChange('gaming');
component.submitSearch();
expect(component.recentSearches()).toEqual(['gaming', 'music']);
});
});