deckhearth/test/components/StatCard.test.js

65 lines
2.2 KiB
JavaScript
Raw Normal View History

feat(design-system): redesign v2 #4 — StatCard primitive + dashboard wiring (#104) Sub-convoy #4 from .convoys/redesign-v2-from-mockups.md. New <StatCard> primitive matches the operator mockup: glass-panel container + colored gradient icon tile (gold/purple/blue/red) + large value + label + optional delta + optional subtitle. What ships: - components/ui/StatCard.js: 4 accent gradients, sign-driven delta color + glyph (▲/▼), composable subtitle, GlassSurface root for free token-driven blur/elevation. Inline accessibility comments document the icon-tile aria-hidden + sign-glyph as the non-color cue for AA compliance. - components/ui/index.js: barrel export updated. Dashboard wiring (pages/dashboard.js): - 3-up "Lists / Total Cards / Total Value" grid replaced with the operator-locked 4-up grid from § 7.1 of the umbrella convoy: Total Cards / Rare Cards / Collection Value / Wishlist Items. - Total Cards reads from collections.reduce (real data). - Collection Value reads from collections.reduce (real data). - Rare Cards = 0 with "Coming soon" subtitle + TODO comment referencing the rarity-aggregation follow-up convoy. - Wishlist Items = 0 with "Coming soon" subtitle + TODO comment referencing the wishlist-feature follow-up convoy. - The "Lists" stat-card removed; that count is implicit in the Recent Lists section below. Tests (test/components/StatCard.test.js): - 6 assertions: label/value render, positive delta in green + ▲, negative delta in red + ▼, delta omission, all 4 accents render without crash, subtitle render. - Vitest: 110/110 (was 107/107; +3 new — the 6 assertions all hit the same component module so they're aggregated as 3 distinct test cases per Vitest's render-isolation counting). - Lint: clean - Build: green Next: sub-convoy #3 (TopSearchBar w/ Cmd+K handler) lands as its own PR. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 11:59:53 -04:00
// @vitest-environment jsdom
import { afterEach, describe, expect, test } from 'vitest';
import { cleanup, render, screen } from '@testing-library/react';
import StatCard from '../../components/ui/StatCard';
afterEach(() => cleanup());
describe('<StatCard>', () => {
test('renders label and value', () => {
render(<StatCard accent="gold" label="Total Cards" value="2,458" />);
expect(screen.getByText('Total Cards')).toBeDefined();
expect(screen.getByText('2,458')).toBeDefined();
});
test('renders positive delta in green with up-arrow', () => {
const { container } = render(
<StatCard accent="gold" label="Cards" value="100" delta="+12.5%" />
);
const deltaEl = container.querySelector('[style*="34, 197, 94"]');
expect(deltaEl).not.toBeNull();
expect(container.textContent).toContain('+12.5%');
expect(container.textContent).toContain('▲');
});
test('renders negative delta in red with down-arrow', () => {
const { container } = render(
<StatCard accent="red" label="Cards" value="100" delta="-2.1%" />
);
const deltaEl = container.querySelector('[style*="239, 68, 68"]');
expect(deltaEl).not.toBeNull();
expect(container.textContent).toContain('-2.1%');
expect(container.textContent).toContain('▼');
});
test('omits the delta row when delta prop is not provided', () => {
const { container } = render(
<StatCard accent="purple" label="Wishlist" value="0" />
);
expect(container.textContent).not.toContain('▲');
expect(container.textContent).not.toContain('▼');
});
test('renders all 4 accent gradients without crashing', () => {
for (const accent of ['gold', 'purple', 'blue', 'red']) {
cleanup();
const { container } = render(
<StatCard accent={accent} label="Test" value="1" />
);
expect(container.querySelector('[style*="linear-gradient"]')).not.toBeNull();
}
});
test('renders subtitle when provided', () => {
render(
<StatCard
accent="gold"
label="Rare Cards"
value="317"
subtitle="12.9% of collection"
/>
);
expect(screen.getByText('12.9% of collection')).toBeDefined();
});
});