f0674ca07d
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>
71 lines
2.6 KiB
JavaScript
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');
|
|
});
|
|
});
|