Files
vyndr/tests/unit/teamHubUI.test.js
T
builtbykev f0674ca07d Session 51: Complete Team Hub (2234 tests)
Research-depth team view: /team/[abbr] with roster, archetypes, stats, props.

- Team API: mlbStatsAdapter.getTeams/resolveTeam/getTeamRoster (statsapi, abbr→id
  + active roster, cached). teamService.getTeamHub assembles roster → per-player
  season stats (bounded concurrency) + archetype (snapshot grade or classify) +
  tonight's graded props from grades:{sport}; whole hub cached 15min. MLB real;
  NBA/WNBA graceful snapshot roster. GET /api/team/:abbr (404 unknown) + proxy.
- Team Hub page: server page.tsx (generateMetadata) + TeamHub client — header,
  sort (archetype/graded/A-Z), archetype filter chips, roster rows (archetype +
  player link + position + stats + graded props + parlay "+"), "No active props"
  greyed state, loading/error.
- Game cards: team abbreviations are now TeamLinks → /team/:abbr?sport= (green
  hover, stops propagation). Team Hub has "← Back to Slate".

Backend 2215 -> 2234 tests (+19), 190 suites. Web build clean (exit 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 11:55:10 -04:00

71 lines
2.6 KiB
JavaScript

// Session 51 — Team Hub page + game-card team links (source-asserted, matching
// the repo's frontend test pattern).
const fs = require('fs');
const path = require('path');
const WEB = path.join(__dirname, '..', '..', 'web', 'src');
const read = (rel) => fs.readFileSync(path.join(WEB, rel), 'utf8');
describe('Team Hub page', () => {
const src = read('app/team/[abbr]/TeamHub.tsx');
const page = read('app/team/[abbr]/page.tsx');
it('fetches /api/team/:abbr and renders team name + roster', () => {
expect(src).toContain('/api/team/');
expect(src).toContain('data.team.name');
expect(src).toContain('roster.map');
});
it('player names link to the player profile', () => {
expect(src).toContain('playerHref(p.player, data.team.sport)');
});
it('has sort (archetype/props/name) + archetype filter', () => {
expect(src).toContain("k=\"archetype\"");
expect(src).toContain("k=\"props\"");
expect(src).toContain('archetypeFilter');
expect(src).toContain('ArchetypeBadge');
});
it('shows "No active props" for players without props (greyed)', () => {
expect(src).toContain('No active props');
expect(src).toContain('opacity: noProps ? 0.6 : 1');
});
it('has back-to-slate navigation', () => {
expect(src).toContain('← Back to Slate');
expect(src).toContain('href="/dashboard"');
});
it('handles loading + error states', () => {
expect(src).toContain("'loading'");
expect(src).toContain("'error'");
expect(src).toContain('Team not found');
});
it('wires the parlay "+" on graded props', () => {
expect(src).toContain('useParlay');
expect(src).toContain('onPropClick');
expect(src).toContain('legKey');
});
it('server page exports generateMetadata with the team abbr', () => {
expect(page).toContain('export async function generateMetadata');
expect(page).toContain('Team Hub');
});
});
describe('Game card team links', () => {
const src = read('components/vyndr/GameCard.tsx');
it('renders team abbreviations as links to /team/:abbr', () => {
expect(src).toContain('function TeamLink');
expect(src).toContain('/team/${encodeURIComponent(abbr)}?sport=');
expect(src).toContain('<TeamLink abbr={g.away.abbr}');
expect(src).toContain('<TeamLink abbr={g.home.abbr}');
});
it('stops propagation so the link does not trigger open-game', () => {
expect(src).toContain('onClick={(e) => e.stopPropagation()}');
});
});
describe('Team API Next proxy', () => {
it('forwards GET /api/team/:abbr to the backend', () => {
const src = read('app/api/team/[abbr]/route.ts');
expect(src).toContain('/api/team/');
expect(src).toContain('BACKEND_URL');
});
});