Add per-story views, trending, and reader reading-experience upgrades
Surface read counts and sharpen the reader: - story_views table + RecordStoryView on /api/article (background, filter-guarded); "Popular this week" home rail via TrendingStories; read-count badge and reading-time chip decorated onto every listing - reader: signed-in-only read-aloud (TTS), native share/copy, and an Aa typography popover (size/serif/sepia) persisted per device - real alt text on card/reader/related/search images; time-of-day Pete greeting on the home hero - harden exec() to skip (not panic) on a nil DB so background writes can't crash on a closed handle Tests: story_views_test.go, trending_test.go. Suite green, CSS rebuilt.
This commit is contained in:
69
internal/web/trending_test.go
Normal file
69
internal/web/trending_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"net/http/httptest"
|
||||
|
||||
"pete/internal/config"
|
||||
"pete/internal/storage"
|
||||
)
|
||||
|
||||
// TestIndexTrendingAndBadges seeds a story with captured content and some reader
|
||||
// views, then asserts the home page renders the "Popular this week" rail plus the
|
||||
// per-card reading-time chip and read-count badge that decorate() fills in.
|
||||
func TestIndexTrendingAndBadges(t *testing.T) {
|
||||
storage.Close()
|
||||
if err := storage.Init(filepath.Join(t.TempDir(), "trending.db")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { storage.Close() })
|
||||
|
||||
// ~1200 chars of body → about a 1 min read.
|
||||
body := strings.Repeat("word ", 240)
|
||||
story := &storage.Story{
|
||||
GUID: "trend-e2e",
|
||||
Headline: "A Trending Headline",
|
||||
Lede: "Lede.",
|
||||
Content: body,
|
||||
ArticleURL: "https://example.com/trend",
|
||||
Source: "Example Wire",
|
||||
Channel: "tech",
|
||||
Classified: true,
|
||||
SeenAt: time.Now().Unix(),
|
||||
}
|
||||
if err := storage.InsertStory(story); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var id int64
|
||||
if err := storage.Get().QueryRow(`SELECT id FROM stories WHERE guid = ?`, story.GUID).Scan(&id); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Two reads so the story trends and the badge shows a count.
|
||||
storage.RecordStoryView(id)
|
||||
storage.RecordStoryView(id)
|
||||
|
||||
s, err := New(config.WebConfig{SiteTitle: "Pete", ListenAddr: ":0"}, nil, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rw := httptest.NewRecorder()
|
||||
s.handleIndex(rw, httptest.NewRequest("GET", "/", nil))
|
||||
if rw.Code != 200 {
|
||||
t.Fatalf("index status = %d", rw.Code)
|
||||
}
|
||||
body = rw.Body.String()
|
||||
|
||||
for _, want := range []string{
|
||||
"Popular this week", // trending rail heading
|
||||
"min read", // reading-time chip
|
||||
`title="2 reads"`, // view-count badge
|
||||
} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("index HTML missing %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user