steam-backlog-enforcer/web/src/test/factories.ts
Krzysztof kuhy Rudnicki 41deb90324 feat: add interactive web UI for backlog completion planning
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>
2026-05-29 18:35:45 +02:00

82 lines
1.8 KiB
TypeScript

// Shared test factories. Lives under src/test/ which is excluded from the
// app build and from coverage.
import type { Filters, WebDataset, WebGame, WebStateInfo } from '../types'
export function makeGame(over: Partial<WebGame> = {}): WebGame {
return {
app_id: 1,
name: 'Game',
completion_pct: 0,
playtime_minutes: 60,
rush_hours: 10,
leisure_hours: 20,
worst_hours: 25,
count_comp: 20,
comp_100_count: 5,
hltb_game_id: 0,
protondb_tier: 'gold',
protondb_trending_tier: 'gold',
protondb_score: 0.8,
...over,
}
}
export function makeFilters(over: Partial<Filters> = {}): Filters {
return {
minCountComp: 15,
minComp100: 3,
minConfidenceSum: 18,
protonMode: 'playable',
protonMinTier: 'gold',
protonTreatMissingAsPass: true,
dailyHours: 4,
basis: 'leisure',
maxHoursPerGame: 0,
playtimeMode: 'all',
includeNoData: false,
fallbackHours: 20,
excluded: new Set<number>(),
search: '',
targetDate: '',
...over,
}
}
export function makeState(over: Partial<WebStateInfo> = {}): WebStateInfo {
return {
current_app_id: null,
current_game_name: '',
games_done: 0,
days_elapsed: 0,
enforcement_started_at: '',
pace_games_per_day: 0,
...over,
}
}
export function makeDataset(
games: WebGame[] = [makeGame()],
over: Partial<WebDataset> = {},
): WebDataset {
return {
games,
state: makeState(),
defaults: {
min_comp_100_polls: 3,
min_count_comp: 15,
min_confidence_sum: 18,
min_playable_tier: 'gold',
hours_per_day_presets: [2, 4, 6, 8],
},
default_summary: {
qualifying: games.length,
rush_total: 0,
leisure_total: 0,
worst_total: 0,
},
generated_at: '2026-05-29T00:00:00+00:00',
...over,
}
}