a18a3f33fc
Part 6 #8 — Desk $44.99 is the hero tier. New DeskShowcase leads the pricing page with the "$1M terminal · $44.99" story + real feature visuals (alt-line ladder, quarter-Kelly, parlay φ, real-time feed) in a balanced two-column layout (kills the dead right-half). Desk is the sole highlighted tier / single primary CTA (color contract #9); Analyst is secondary. Real prices: Free 5 scans, Analyst $14.99/$19.99, Desk $34.99/$44.99. ClaimMeter + Stripe checkout wiring untouched. Part 4 #7 — Ticker → punctuated stillness. The continuous marquee is retired; the ticker now RESTS ≥4s on each ranked item and pulses only on change. The EKG heartbeat is a static readout — the header's ONE idle proof-of-life is the single SIGNAL-LIVE live-dot (the ticker dropped its competing pulse). All durations tokenized (--motion-*, --ticker-hold); prefers-reduced-motion kills the motion entirely. Part 8 #20 — new EmptyState component modeled on the north-star 404 (scanlines + glitch wordmark + amber system voice + CTA hierarchy), reused at the bare-red "Team not found", "Game not found", and the ledger empties — one unified voice. Part 5 — archetype glyph+chip propagated to STREAKS rows + ledger rows (optional + self-hiding; absent beats fabricated); grade reveal already carries ArchetypeBlend. Tests: new tests/unit/ds5PricingStates.test.js (22) locks all four workstreams; updated teamHubUI + vyndrDesignSystem for the new surfaces. 237 suites / 2864 tests green; next build exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
74 lines
2.8 KiB
JavaScript
74 lines
2.8 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', () => {
|
|
// DS5 (#20) — the error surface is the unified EmptyState; its action links
|
|
// back to the Slate.
|
|
expect(src).toContain('← Back to the Slate');
|
|
expect(src).toContain("href: '/dashboard'");
|
|
});
|
|
it('handles loading + error states via the unified EmptyState (no bare-red line)', () => {
|
|
expect(src).toContain("'loading'");
|
|
expect(src).toContain("'error'");
|
|
expect(src).toContain('<EmptyState');
|
|
expect(src).toContain('code="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');
|
|
});
|
|
});
|