mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-25 21:43:42 +00:00
Merge branch 'areas-meshguide-sync'
This commit is contained in:
@@ -426,6 +426,17 @@ func componentSchemas() map[string]interface{} {
|
||||
"kmPerSecondAirtime": map[string]interface{}{"type": "number", "nullable": true, "description": "farthestKm / (airtimeMs/1000) -- geographic distance covered per second of estimated RF airtime spent relaying this ping. Only set when both farthestKm and airtimeMs (with relayCount>0) are available."},
|
||||
},
|
||||
},
|
||||
"WeeklyPingRecords": map[string]interface{}{
|
||||
"type": "object",
|
||||
"description": "Mirrors PingScoresResponse's 5 all-time record slots, scoped to the trailing 7 days -- an achievable target that resets on its own, instead of a slot that locks in forever once someone sets a big all-time record.",
|
||||
"properties": map[string]interface{}{
|
||||
"farthestPing": schemaRef("PingScore"),
|
||||
"mostHopsPing": schemaRef("PingScore"),
|
||||
"widestSpreadPing": schemaRef("PingScore"),
|
||||
"fastestSpreadPing": map[string]interface{}{"allOf": []interface{}{schemaRef("PingScore")}, "description": "Same >=2-station rule as the all-time fastestSpreadPing, applied within the 7-day window."},
|
||||
"mostEfficientPing": schemaRef("PingScore"),
|
||||
},
|
||||
},
|
||||
"PingLeaderboardEntry": map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
@@ -445,6 +456,7 @@ func componentSchemas() map[string]interface{} {
|
||||
"widestSpreadPing": schemaRef("PingScore"),
|
||||
"fastestSpreadPing": map[string]interface{}{"allOf": []interface{}{schemaRef("PingScore")}, "description": "The fastest full spread among pings heard by at least 2 stations -- a lone station is trivially \"instant\" and is excluded so it can't win this record for nothing."},
|
||||
"mostEfficientPing": schemaRef("PingScore"),
|
||||
"thisWeek": map[string]interface{}{"allOf": []interface{}{schemaRef("WeeklyPingRecords")}, "description": "The same 5 records as above, scoped to the trailing 7 days instead of all-time. Omitted when no ping in the last 7 days resolved to a usable score."},
|
||||
"relayLeaderboard": map[string]interface{}{"type": "array", "items": schemaRef("PingLeaderboardEntry"), "description": "Top nodes ranked by number of distinct pings they appeared as a relay hop in (deduped per ping first, so one busy ping's many branches can't over-credit a relay)."},
|
||||
"observerLeaderboard": map[string]interface{}{"type": "array", "items": schemaRef("PingLeaderboardEntry"), "description": "Top observers ranked by number of pings they were the first station to hear."},
|
||||
"senderLeaderboard": map[string]interface{}{"type": "array", "items": schemaRef("PingLeaderboardEntry"), "description": "Top senders ranked by number of pings sent in the last 30 days (unlike the other leaderboards and records, which are all-time). Keyed by the sender display name from the channel message itself -- no resolved pubkey, so entries never carry one."},
|
||||
|
||||
+83
-27
@@ -86,6 +86,13 @@ type PingScoresSnapshot struct {
|
||||
FastestSpreadPing *PingScore `json:"fastestSpreadPing,omitempty"`
|
||||
MostEfficientPing *PingScore `json:"mostEfficientPing,omitempty"`
|
||||
|
||||
// ThisWeek mirrors the 5 all-time records above but scoped to the
|
||||
// trailing 7 days -- an all-time record set once (e.g. a 364km
|
||||
// farthest ping) locks that slot forever, so this gives people an
|
||||
// achievable target that resets on its own (dborup, 2026-07-28).
|
||||
// nil when no ping in the last 7 days resolved to a usable score.
|
||||
ThisWeek *WeeklyPingRecords `json:"thisWeek,omitempty"`
|
||||
|
||||
// RelayLeaderboard ranks nodes by how many DISTINCT pings they
|
||||
// appeared as a relay hop in (deduped per ping first, so one busy
|
||||
// ping's many branches can't over-credit a relay that appears in
|
||||
@@ -105,6 +112,16 @@ type PingScoresSnapshot struct {
|
||||
SenderLeaderboard []PingLeaderboardEntry `json:"senderLeaderboard,omitempty"`
|
||||
}
|
||||
|
||||
// WeeklyPingRecords mirrors PingScoresSnapshot's 5 all-time record slots,
|
||||
// scoped to the trailing 7 days.
|
||||
type WeeklyPingRecords struct {
|
||||
FarthestPing *PingScore `json:"farthestPing,omitempty"`
|
||||
MostHopsPing *PingScore `json:"mostHopsPing,omitempty"`
|
||||
WidestSpreadPing *PingScore `json:"widestSpreadPing,omitempty"`
|
||||
FastestSpreadPing *PingScore `json:"fastestSpreadPing,omitempty"`
|
||||
MostEfficientPing *PingScore `json:"mostEfficientPing,omitempty"`
|
||||
}
|
||||
|
||||
type pingTriggerRow struct {
|
||||
txID int64
|
||||
hash string
|
||||
@@ -211,6 +228,40 @@ func (s *Server) computePingScore(trigger pingTriggerRow) *PingScore {
|
||||
return score
|
||||
}
|
||||
|
||||
// pingRecordSet accumulates the same 5 "best of" record slots used by
|
||||
// both the all-time and this-week views, so the selection rules only
|
||||
// need to be written once.
|
||||
type pingRecordSet struct {
|
||||
Farthest *PingScore
|
||||
MostHops *PingScore
|
||||
WidestSpread *PingScore
|
||||
FastestSpread *PingScore
|
||||
MostEfficient *PingScore
|
||||
}
|
||||
|
||||
func (rs *pingRecordSet) consider(score *PingScore) {
|
||||
if score.FarthestKm != nil && (rs.Farthest == nil || rs.Farthest.FarthestKm == nil || *score.FarthestKm > *rs.Farthest.FarthestKm) {
|
||||
rs.Farthest = score
|
||||
}
|
||||
if rs.MostHops == nil || score.DeepestHops > rs.MostHops.DeepestHops {
|
||||
rs.MostHops = score
|
||||
}
|
||||
if rs.WidestSpread == nil || score.StationCount > rs.WidestSpread.StationCount {
|
||||
rs.WidestSpread = score
|
||||
}
|
||||
// Fastest full spread only makes sense with a real multi-station
|
||||
// spread to measure -- a lone station is trivially "instant" and
|
||||
// would otherwise always win this record for nothing.
|
||||
if score.SpreadSeconds != nil && score.StationCount >= 2 &&
|
||||
(rs.FastestSpread == nil || rs.FastestSpread.SpreadSeconds == nil || *score.SpreadSeconds < *rs.FastestSpread.SpreadSeconds) {
|
||||
rs.FastestSpread = score
|
||||
}
|
||||
if score.KmPerSecondAirtime != nil &&
|
||||
(rs.MostEfficient == nil || rs.MostEfficient.KmPerSecondAirtime == nil || *score.KmPerSecondAirtime > *rs.MostEfficient.KmPerSecondAirtime) {
|
||||
rs.MostEfficient = score
|
||||
}
|
||||
}
|
||||
|
||||
// computeAllPingScores computes the full snapshot: records + leaderboards.
|
||||
func (s *Server) computeAllPingScores() *PingScoresSnapshot {
|
||||
triggers, err := s.db.fetchPingTriggers()
|
||||
@@ -236,31 +287,23 @@ func (s *Server) computeAllPingScores() *PingScoresSnapshot {
|
||||
senderCutoff := time.Now().AddDate(0, 0, -30)
|
||||
senderCounts := map[string]*PingLeaderboardEntry{}
|
||||
|
||||
// weekCutoff drives ThisWeek -- same fail-toward-stale rule as
|
||||
// senderCutoff above (an unparseable timestamp is excluded, not
|
||||
// defaulted to included).
|
||||
weekCutoff := time.Now().AddDate(0, 0, -7)
|
||||
allTime := &pingRecordSet{}
|
||||
week := &pingRecordSet{}
|
||||
|
||||
for _, trigger := range triggers {
|
||||
score := s.computePingScore(trigger)
|
||||
if score == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if score.FarthestKm != nil && (snap.FarthestPing == nil || snap.FarthestPing.FarthestKm == nil || *score.FarthestKm > *snap.FarthestPing.FarthestKm) {
|
||||
snap.FarthestPing = score
|
||||
}
|
||||
if snap.MostHopsPing == nil || score.DeepestHops > snap.MostHopsPing.DeepestHops {
|
||||
snap.MostHopsPing = score
|
||||
}
|
||||
if snap.WidestSpreadPing == nil || score.StationCount > snap.WidestSpreadPing.StationCount {
|
||||
snap.WidestSpreadPing = score
|
||||
}
|
||||
// Fastest full spread only makes sense with a real multi-station
|
||||
// spread to measure -- a lone station is trivially "instant" and
|
||||
// would otherwise always win this record for nothing.
|
||||
if score.SpreadSeconds != nil && score.StationCount >= 2 &&
|
||||
(snap.FastestSpreadPing == nil || snap.FastestSpreadPing.SpreadSeconds == nil || *score.SpreadSeconds < *snap.FastestSpreadPing.SpreadSeconds) {
|
||||
snap.FastestSpreadPing = score
|
||||
}
|
||||
if score.KmPerSecondAirtime != nil &&
|
||||
(snap.MostEfficientPing == nil || snap.MostEfficientPing.KmPerSecondAirtime == nil || *score.KmPerSecondAirtime > *snap.MostEfficientPing.KmPerSecondAirtime) {
|
||||
snap.MostEfficientPing = score
|
||||
allTime.consider(score)
|
||||
ts, tsErr := time.Parse(time.RFC3339, score.Timestamp)
|
||||
if tsErr == nil && ts.After(weekCutoff) {
|
||||
week.consider(score)
|
||||
}
|
||||
|
||||
for _, pk := range score.relayPubkeys {
|
||||
@@ -279,15 +322,28 @@ func (s *Server) computeAllPingScores() *PingScoresSnapshot {
|
||||
}
|
||||
e.Count++
|
||||
}
|
||||
if score.Sender != "" {
|
||||
if ts, err := time.Parse(time.RFC3339, score.Timestamp); err == nil && ts.After(senderCutoff) {
|
||||
e := senderCounts[score.Sender]
|
||||
if e == nil {
|
||||
e = &PingLeaderboardEntry{Name: score.Sender}
|
||||
senderCounts[score.Sender] = e
|
||||
}
|
||||
e.Count++
|
||||
if score.Sender != "" && tsErr == nil && ts.After(senderCutoff) {
|
||||
e := senderCounts[score.Sender]
|
||||
if e == nil {
|
||||
e = &PingLeaderboardEntry{Name: score.Sender}
|
||||
senderCounts[score.Sender] = e
|
||||
}
|
||||
e.Count++
|
||||
}
|
||||
}
|
||||
|
||||
snap.FarthestPing = allTime.Farthest
|
||||
snap.MostHopsPing = allTime.MostHops
|
||||
snap.WidestSpreadPing = allTime.WidestSpread
|
||||
snap.FastestSpreadPing = allTime.FastestSpread
|
||||
snap.MostEfficientPing = allTime.MostEfficient
|
||||
if week.Farthest != nil || week.MostHops != nil || week.WidestSpread != nil || week.FastestSpread != nil || week.MostEfficient != nil {
|
||||
snap.ThisWeek = &WeeklyPingRecords{
|
||||
FarthestPing: week.Farthest,
|
||||
MostHopsPing: week.MostHops,
|
||||
WidestSpreadPing: week.WidestSpread,
|
||||
FastestSpreadPing: week.FastestSpread,
|
||||
MostEfficientPing: week.MostEfficient,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -318,6 +318,63 @@ func TestComputeAllPingScores_SenderLeaderboard30DayCutoff(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestComputeAllPingScores_ThisWeek confirms ThisWeek is an independently
|
||||
// windowed record set: an older, BIGGER ping still wins the all-time slot,
|
||||
// while a smaller but recent ping wins the equivalent ThisWeek slot since
|
||||
// the older one falls outside the 7-day window.
|
||||
func TestComputeAllPingScores_ThisWeek(t *testing.T) {
|
||||
srv, _ := setupPingScoresFixture(t)
|
||||
|
||||
// Old: 10 days ago, farther (pingobsb, ~230km from pingobsa) -- must
|
||||
// win the all-time FarthestPing but be excluded from ThisWeek.
|
||||
oldTs := time.Now().AddDate(0, 0, -10)
|
||||
txOld := seedPingTrigger(t, srv, "weekold0000001", "#test", "Alice", oldTs.Format(time.RFC3339))
|
||||
seedPingObservation(t, srv, txOld, "pingobsa", 9.0, `[]`, `[]`, oldTs.Unix())
|
||||
seedPingObservation(t, srv, txOld, "pingobsb", 6.0, `["aa"]`, `["pkrelay1"]`, oldTs.Unix()+10)
|
||||
|
||||
// Recent: 2 days ago, closer (pingobsc, ~6km from pingobsa) -- the
|
||||
// only ping inside the 7-day window, so it must win ThisWeek's
|
||||
// FarthestPing even though it's smaller than the old one.
|
||||
recentTs := time.Now().AddDate(0, 0, -2)
|
||||
txRecent := seedPingTrigger(t, srv, "weeknew0000001", "#test", "Bob", recentTs.Format(time.RFC3339))
|
||||
seedPingObservation(t, srv, txRecent, "pingobsa", 9.0, `[]`, `[]`, recentTs.Unix())
|
||||
seedPingObservation(t, srv, txRecent, "pingobsc", 6.0, `["aa"]`, `["pkrelay2"]`, recentTs.Unix()+10)
|
||||
|
||||
snap := srv.computeAllPingScores()
|
||||
if snap == nil {
|
||||
t.Fatal("computeAllPingScores returned nil")
|
||||
}
|
||||
if snap.FarthestPing == nil || snap.FarthestPing.Hash != "weekold0000001" {
|
||||
t.Errorf("all-time FarthestPing = %+v, want weekold0000001 (the farther, older ping)", snap.FarthestPing)
|
||||
}
|
||||
if snap.ThisWeek == nil {
|
||||
t.Fatal("ThisWeek is nil, want a populated record set from the recent ping")
|
||||
}
|
||||
if snap.ThisWeek.FarthestPing == nil || snap.ThisWeek.FarthestPing.Hash != "weeknew0000001" {
|
||||
t.Errorf("ThisWeek.FarthestPing = %+v, want weeknew0000001 -- the old ping is outside the 7-day window", snap.ThisWeek.FarthestPing)
|
||||
}
|
||||
}
|
||||
|
||||
// TestComputeAllPingScores_ThisWeekNilWhenNoRecentPings confirms ThisWeek
|
||||
// stays nil (not a zero-valued struct) when every ping is outside the
|
||||
// 7-day window, matching the frontend's null-safe "no record yet"
|
||||
// rendering rather than an empty-but-present object.
|
||||
func TestComputeAllPingScores_ThisWeekNilWhenNoRecentPings(t *testing.T) {
|
||||
srv, _ := setupPingScoresFixture(t)
|
||||
oldTs := time.Now().AddDate(0, 0, -30)
|
||||
txOld := seedPingTrigger(t, srv, "weeknone000001", "#test", "Alice", oldTs.Format(time.RFC3339))
|
||||
seedPingObservation(t, srv, txOld, "pingobsa", 9.0, `[]`, `[]`, oldTs.Unix())
|
||||
seedPingObservation(t, srv, txOld, "pingobsb", 6.0, `["aa"]`, `["pkrelay1"]`, oldTs.Unix()+10)
|
||||
|
||||
snap := srv.computeAllPingScores()
|
||||
if snap == nil {
|
||||
t.Fatal("computeAllPingScores returned nil")
|
||||
}
|
||||
if snap.ThisWeek != nil {
|
||||
t.Errorf("ThisWeek = %+v, want nil when no ping in the last 7 days resolved to a usable score", snap.ThisWeek)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandlePingScores_EmptyState confirms the endpoint returns a
|
||||
// well-formed 200 with zero-valued/omitted fields rather than an error
|
||||
// when no ping has ever been recorded -- an ordinary state, not a failure.
|
||||
|
||||
@@ -119,10 +119,25 @@
|
||||
return recordCardHtml(def, data[def.key]);
|
||||
}).join('');
|
||||
|
||||
// ThisWeek mirrors the same 5 slots, scoped to the trailing 7 days --
|
||||
// an all-time record set once (e.g. a 364km farthest ping) locks that
|
||||
// card in forever, so this gives people an achievable target that
|
||||
// resets on its own. May be entirely absent (no ping in 7 days), in
|
||||
// which case every card falls through to recordCardHtml's "No record
|
||||
// yet" placeholder via the `|| {}` fallback.
|
||||
var week = data.thisWeek || {};
|
||||
var weekHtml = recordDefs.map(function (def) {
|
||||
return recordCardHtml(def, week[def.key]);
|
||||
}).join('');
|
||||
|
||||
container.innerHTML =
|
||||
'<div class="ping-scores-page">' +
|
||||
'<h2>' + phIcon('trophy') + ' Ping Scores</h2>' +
|
||||
'<p class="text-muted">Global records and leaderboards from every "ping" sent in any channel (' + data.totalPings + ' total). Not scoped by region. Updated ' + escapeHtml(formatAgo(data.generatedAt)) + '.</p>' +
|
||||
'<h3>' + phIcon('arrow-clockwise') + ' This Week\'s Best</h3>' +
|
||||
'<p class="text-muted" style="font-size:0.85em">Resets on its own after 7 days.</p>' +
|
||||
'<div class="stats-grid ps-records-grid ps-week-grid">' + weekHtml + '</div>' +
|
||||
'<h3>All-Time Records</h3>' +
|
||||
'<div class="stats-grid ps-records-grid">' + recordsHtml + '</div>' +
|
||||
'<div class="ps-leaderboards-grid">' +
|
||||
leaderboardTableHtml('Top Senders (30 days)', phIcon('megaphone'), data.senderLeaderboard, 'Sender') +
|
||||
|
||||
@@ -4517,9 +4517,12 @@ th.sort-active { color: var(--accent, #60a5fa); }
|
||||
|
||||
/* ── Ping Scores page (cmd/server/ping_scores.go, GET /api/ping-scores) ── */
|
||||
.ping-scores-page { padding: 16px; max-width: 1100px; margin: 0 auto; }
|
||||
.ping-scores-page h3 { margin: 20px 0 4px 0; font-size: 16px; }
|
||||
.ping-scores-page h3:first-of-type { margin-top: 12px; }
|
||||
.ps-records-grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
|
||||
.ps-record-card { position: relative; }
|
||||
.ps-record-card.ps-empty { opacity: 0.6; }
|
||||
.ps-week-grid .ps-record-card { border-left: 3px solid var(--accent); padding-left: 13px; }
|
||||
.ps-record-meta { font-size: 12px; color: var(--text-muted); margin-top: 4px; }
|
||||
.ps-record-desc { font-size: 11px; color: var(--text-muted); margin-top: 6px; line-height: 1.4; }
|
||||
.ps-leaderboards-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; margin-top: 8px; }
|
||||
|
||||
+35
-1
@@ -124,24 +124,58 @@ function makeSandbox(apiImpl) {
|
||||
widestSpreadPing: { hash: 'wide0001', sender: 'Carol', timestamp: new Date().toISOString(), stationCount: 6, deepestHops: 3 },
|
||||
fastestSpreadPing: { hash: 'fast0001', sender: 'Dave', timestamp: new Date().toISOString(), spreadSeconds: 2.5, stationCount: 2, deepestHops: 1 },
|
||||
mostEfficientPing: { hash: 'eff0001', sender: 'Eve', timestamp: new Date().toISOString(), kmPerSecondAirtime: 50.2, farthestKm: 100, stationCount: 2, deepestHops: 1 },
|
||||
thisWeek: {
|
||||
farthestPing: { hash: 'wkfar0001', sender: 'Frank', timestamp: new Date().toISOString(), farthestKm: 12.3, farthestNodeName: 'WeeklyRepeater', stationCount: 2, deepestHops: 1 },
|
||||
},
|
||||
relayLeaderboard: [{ pubkey: 'pkrelay1', name: 'RelayOne', count: 7 }],
|
||||
observerLeaderboard: [{ pubkey: 'pkobs1', name: 'ObsOne', count: 3 }],
|
||||
senderLeaderboard: [{ name: 'PingMaster', count: 12 }],
|
||||
};
|
||||
const { container, getPage } = makeSandbox(() => Promise.resolve(data));
|
||||
await getPage().init(container);
|
||||
assert.ok(container.innerHTML.includes('123.4'), 'should show the farthest record km, got: ' + container.innerHTML);
|
||||
assert.ok(container.innerHTML.includes('123.4'), 'should show the all-time farthest record km, got: ' + container.innerHTML);
|
||||
assert.ok(container.innerHTML.includes('4 hops'), 'should show the most-hops record, got: ' + container.innerHTML);
|
||||
assert.ok(container.innerHTML.includes('6 stations'), 'should show the widest-spread record, got: ' + container.innerHTML);
|
||||
assert.ok(container.innerHTML.includes('RelayOne'), 'should show the relay leaderboard entry, got: ' + container.innerHTML);
|
||||
assert.ok(container.innerHTML.includes('ObsOne'), 'should show the observer leaderboard entry, got: ' + container.innerHTML);
|
||||
assert.ok(container.innerHTML.includes('PingMaster'), 'should show the sender leaderboard entry, got: ' + container.innerHTML);
|
||||
assert.ok(container.innerHTML.includes('Top Senders (30 days)'), 'should show the Top Senders leaderboard heading with the 30-day window noted, got: ' + container.innerHTML);
|
||||
assert.ok(container.innerHTML.includes("This Week's Best"), 'should show the This Week\'s Best section heading, got: ' + container.innerHTML);
|
||||
assert.ok(container.innerHTML.includes('All-Time Records'), 'should show the All-Time Records section heading, got: ' + container.innerHTML);
|
||||
assert.ok(container.innerHTML.includes('12.3'), 'should show the thisWeek farthest record km distinct from the all-time one, got: ' + container.innerHTML);
|
||||
assert.ok(container.innerHTML.includes('WeeklyRepeater'), 'should show the thisWeek record\'s node name, got: ' + container.innerHTML);
|
||||
passed++;
|
||||
console.log(' ✅ renders all 5 records and all three leaderboards (including Top Senders) from a populated response');
|
||||
} catch (e) { failed++; console.log(' ❌ renders all 5 records and all three leaderboards (including Top Senders) from a populated response: ' + e.message); }
|
||||
})();
|
||||
|
||||
await (async () => {
|
||||
try {
|
||||
// thisWeek is entirely absent when no ping in the last 7 days
|
||||
// resolved to a usable score (cmd/server/ping_scores.go leaves it
|
||||
// nil rather than sending an empty object) -- the section must
|
||||
// still render its 5 "No record yet" placeholders, not throw. All 5
|
||||
// all-time slots are populated here so the only "No record yet"
|
||||
// cards left are the 5 thisWeek ones -- isolates what's under test.
|
||||
const data = {
|
||||
totalPings: 1,
|
||||
generatedAt: new Date().toISOString(),
|
||||
farthestPing: { hash: 'old0001', sender: 'Grace', timestamp: new Date().toISOString(), farthestKm: 300, stationCount: 2, deepestHops: 1 },
|
||||
mostHopsPing: { hash: 'old0002', sender: 'Grace', timestamp: new Date().toISOString(), deepestHops: 3, stationCount: 2 },
|
||||
widestSpreadPing: { hash: 'old0003', sender: 'Grace', timestamp: new Date().toISOString(), stationCount: 4, deepestHops: 1 },
|
||||
fastestSpreadPing: { hash: 'old0004', sender: 'Grace', timestamp: new Date().toISOString(), spreadSeconds: 3, stationCount: 2, deepestHops: 1 },
|
||||
mostEfficientPing: { hash: 'old0005', sender: 'Grace', timestamp: new Date().toISOString(), kmPerSecondAirtime: 10, farthestKm: 20, stationCount: 2, deepestHops: 1 },
|
||||
};
|
||||
const { container, getPage } = makeSandbox(() => Promise.resolve(data));
|
||||
await getPage().init(container);
|
||||
assert.ok(container.innerHTML.includes("This Week's Best"), 'should still show the This Week\'s Best heading, got: ' + container.innerHTML);
|
||||
const weekSectionCount = (container.innerHTML.match(/No record yet/g) || []).length;
|
||||
assert.strictEqual(weekSectionCount, 5, 'expected all 5 thisWeek cards to fall back to "No record yet", got ' + weekSectionCount);
|
||||
passed++;
|
||||
console.log(' ✅ missing thisWeek renders 5 "No record yet" placeholders instead of throwing');
|
||||
} catch (e) { failed++; console.log(' ❌ missing thisWeek renders 5 "No record yet" placeholders instead of throwing: ' + e.message); }
|
||||
})();
|
||||
|
||||
await (async () => {
|
||||
try {
|
||||
// Sender entries never carry a pubkey (see cmd/server/ping_scores.go
|
||||
|
||||
Reference in New Issue
Block a user