mirror of
https://github.com/kuhyx/steam-backlog-enforcer.git
synced 2026-11-11 22:53:48 +01:00
Adds a React/TypeScript frontend (web/) with a Python stdlib HTTP server backend. The UI mirrors the CLI `stats` command in the browser, with real-time sliders for ProtonDB rating, HLTB confidence thresholds, daily play time, per-game time cap, playtime mode, no-HLTB-data fallback, and a target-date planner. A parity badge confirms the client-side totals reproduce the CLI defaults exactly (786 / 67031.1h / 143017.2h / 238447.9h). Python side: - `_web_dataset.py`: offline projection of HLTB/ProtonDB/snapshot caches into a compact, secret-free JSON payload; 100% branch coverage - `_web_server.py`: zero-dependency stdlib HTTP server serving the built Vite bundle and the /api/dataset endpoint; 100% branch coverage - `main.py`: new `serve` command wiring Frontend (Vitest + RTL, 100% branch coverage enforced): - TypeScript port of ProtonDB compound rating rule with full parity - Pure client-side filtering via estimate.ts (no server round-trips) - SVG completion timeline chart, sortable/searchable game table - Steam dark theme; responsive layout Pre-commit: adds `vitest-coverage` hook at pre-push stage requiring 100% branch coverage on the React codebase. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
58 lines
2.0 KiB
TypeScript
58 lines
2.0 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { isPlayable, passesMinTier, tierLabel } from './protondb'
|
|
|
|
describe('isPlayable (faithful CLI compound rule)', () => {
|
|
it('keeps games with no rating or pending', () => {
|
|
expect(isPlayable('', '')).toBe(true)
|
|
expect(isPlayable('pending', '')).toBe(true)
|
|
})
|
|
|
|
it('single rating must be gold-or-better', () => {
|
|
expect(isPlayable('platinum', '')).toBe(true)
|
|
expect(isPlayable('gold', '')).toBe(true)
|
|
expect(isPlayable('silver', '')).toBe(false)
|
|
})
|
|
|
|
it('rejects when either rating is below silver', () => {
|
|
expect(isPlayable('gold', 'bronze')).toBe(false)
|
|
expect(isPlayable('bronze', 'gold')).toBe(false)
|
|
})
|
|
|
|
it('rejects when neither rating reaches gold', () => {
|
|
expect(isPlayable('silver', 'silver')).toBe(false)
|
|
})
|
|
|
|
it('accepts when one is gold-or-better and the other silver-or-better', () => {
|
|
expect(isPlayable('gold', 'silver')).toBe(true)
|
|
expect(isPlayable('silver', 'gold')).toBe(true)
|
|
expect(isPlayable('platinum', 'platinum')).toBe(true)
|
|
})
|
|
|
|
it('treats unknown tiers as below silver', () => {
|
|
expect(isPlayable('mystery', 'mystery')).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('passesMinTier', () => {
|
|
it('honours treatMissingAsPass when no data', () => {
|
|
expect(passesMinTier('', '', 'gold', true)).toBe(true)
|
|
expect(passesMinTier('', '', 'gold', false)).toBe(false)
|
|
expect(passesMinTier('pending', 'pending', 'gold', false)).toBe(false)
|
|
})
|
|
|
|
it('uses the best of the two ratings', () => {
|
|
expect(passesMinTier('silver', 'gold', 'gold', false)).toBe(true)
|
|
expect(passesMinTier('silver', 'silver', 'gold', false)).toBe(false)
|
|
expect(passesMinTier('platinum', '', 'gold', false)).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('tierLabel', () => {
|
|
it('renders a dash, single, or paired label', () => {
|
|
expect(tierLabel('', '')).toBe('—')
|
|
expect(tierLabel('gold', 'gold')).toBe('gold')
|
|
expect(tierLabel('gold', '')).toBe('gold')
|
|
expect(tierLabel('gold', 'silver')).toBe('gold / silver')
|
|
})
|
|
})
|