feat: complete frozen MVP — period-aware stats, layout-driven UI, quick checkin
- Fix first daily record 422 (expectedVersion now accepts 0)
- Add period_window + summarize; /api/summary and /api/tasks/{id}/summary
aggregate each metric inside its own day/week/month/lifetime window,
reading raw facts only (never the rebuildable month archives)
- Drive frontend rendering from per-page layout JSON with a component
registry; scope/cardinality enforced server-side, invalid layouts fall back
- Add quick checkin and duration input in H:M:S that round-trips integer seconds
- Fix heatmap/completedAt timezone drift to Asia/Shanghai
- Add server-side session TTL (30d) matching the login cookie
- Handle date.min/date.max boundaries without OverflowError
- Half-up rounding to match browser Math.round
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
42
tests/test_layouts.py
Normal file
42
tests/test_layouts.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from app.layouts import load_layout, validate_layout
|
||||
|
||||
|
||||
def _layout(page, main_components, sidebar=None):
|
||||
return {
|
||||
"schemaVersion": 1,
|
||||
"page": page,
|
||||
"regions": {
|
||||
"sidebar": sidebar or ([{"component": "task-list"}] if page == "task-detail" else []),
|
||||
"main": main_components,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_valid_home_layout():
|
||||
assert validate_layout(_layout("home", [{"component": "overview-summary"}, {"component": "task-progress-grid"}]), "home")
|
||||
|
||||
|
||||
def test_valid_task_detail_layout():
|
||||
assert validate_layout(_layout("task-detail", [{"component": "task-header"}, {"component": "today-record"}, {"component": "metric-progress"}, {"component": "activity-heatmap"}, {"component": "task-settings"}]), "task-detail")
|
||||
|
||||
|
||||
def test_rejects_component_on_wrong_page():
|
||||
assert not validate_layout(_layout("home", [{"component": "today-record"}]), "home")
|
||||
|
||||
|
||||
def test_rejects_component_in_wrong_region():
|
||||
layout = _layout("task-detail", [{"component": "task-header"}], sidebar=[{"component": "task-header"}])
|
||||
assert not validate_layout(layout, "task-detail")
|
||||
|
||||
|
||||
def test_rejects_duplicate_component():
|
||||
layout = _layout("task-detail", [{"component": "today-record"}, {"component": "today-record"}])
|
||||
assert not validate_layout(layout, "task-detail")
|
||||
|
||||
|
||||
def test_load_layout_falls_back_on_invalid(tmp_path):
|
||||
import json
|
||||
bad = tmp_path / "home.json"
|
||||
bad.write_text(json.dumps(_layout("home", [{"component": "today-record"}])), encoding="utf-8")
|
||||
loaded = load_layout(tmp_path, "home")
|
||||
assert loaded["regions"]["main"] != [{"component": "today-record"}]
|
||||
Reference in New Issue
Block a user