refactor: aufgeraeumt — Pakete hinten, Widgets vorne
Go: store, auth, weather und config sind eigene Pakete unter internal/. Vorher lagen fuenf Dateien in einem Topf, und jede kannte jede. Web: os.js hatte 1425 Zeilen und alles darin. Jetzt core.js (Zustand, Server, Einstellungen), glyphs.js (die Pixelraster), js/widgets/ mit einem Widget je Datei, os.js nur noch die Ansichten. Stylesheet nach css/. Die HoneyBar bleibt eine einzelne Datei ohne Importe — sie wird von fremden Seiten mit einer Zeile eingebunden, da darf nichts nachladen. Nebenbei: Das gebaute Binary lag mit 9,8 MB im Repo und ist jetzt ignoriert. Und der Test gegen eine zweite Versionsnummer geht nicht mehr auf os.js, sondern durch alle .js — sonst haette ihn dieser Umbau still ausgehebelt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d08b0a229c
commit
52deffa30e
20 files changed
+780
-624
No files matched your search
@@ -0,0 +1,116 @@
|
||||
package weather
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Ein erfundener Wetterdienst. Kein Test in diesem Projekt fragt je wirklich
|
||||
// bei Open-Meteo an: Das wäre langsam, unzuverlässig und würde einen fremden
|
||||
// Dienst für unsere Testläufe benutzen.
|
||||
func fakeWetter(t *testing.T, code int, grad float64, aufrufe *int32) *Dienst {
|
||||
t.Helper()
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if aufrufe != nil {
|
||||
atomic.AddInt32(aufrufe, 1)
|
||||
}
|
||||
if strings.Contains(r.URL.Path, "search") {
|
||||
json.NewEncoder(w).Encode(map[string]any{"results": []map[string]any{
|
||||
{"name": "Köln", "country": "Deutschland", "admin1": "Nordrhein-Westfalen",
|
||||
"latitude": 50.938, "longitude": 6.96},
|
||||
}})
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(map[string]any{"current": map[string]any{
|
||||
"temperature_2m": grad, "weather_code": code,
|
||||
}})
|
||||
}))
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
d := Neu()
|
||||
d.wetterURL = srv.URL + "/forecast"
|
||||
d.sucheURL = srv.URL + "/search"
|
||||
return d
|
||||
}
|
||||
|
||||
func TestWetterKommtAn(t *testing.T) {
|
||||
d := fakeWetter(t, 61, 12.4, nil)
|
||||
w, err := d.Jetzt(context.Background(), "Köln", 50.9, 6.9)
|
||||
if err != nil {
|
||||
t.Fatalf("unerwartet: %v", err)
|
||||
}
|
||||
if w.Ort != "Köln" || w.Grad != 12.4 {
|
||||
t.Fatalf("falsche Werte: %+v", w)
|
||||
}
|
||||
if w.Glyph != "regen" || w.Text != "regen" {
|
||||
t.Fatalf("Code 61 ist Regen, nicht %q/%q", w.Glyph, w.Text)
|
||||
}
|
||||
}
|
||||
|
||||
// Der Zwischenspeicher ist der Grund, warum die Leiste auf jeder Seite jeder App
|
||||
// stehen darf: Ohne ihn ginge bei jedem Seitenaufruf eine Anfrage nach draußen.
|
||||
func TestZweiteFrageGehtNichtNachDraussen(t *testing.T) {
|
||||
var aufrufe int32
|
||||
d := fakeWetter(t, 0, 21, &aufrufe)
|
||||
for i := 0; i < 5; i++ {
|
||||
if _, err := d.Jetzt(context.Background(), "Köln", 50.9, 6.9); err != nil {
|
||||
t.Fatalf("unerwartet: %v", err)
|
||||
}
|
||||
}
|
||||
if aufrufe != 1 {
|
||||
t.Fatalf("%d Anfragen nach draußen, erwartet 1", aufrufe)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAndererOrtWirdGetrenntGehalten(t *testing.T) {
|
||||
var aufrufe int32
|
||||
d := fakeWetter(t, 0, 21, &aufrufe)
|
||||
d.Jetzt(context.Background(), "Köln", 50.9, 6.9)
|
||||
d.Jetzt(context.Background(), "Wien", 48.2, 16.4)
|
||||
if aufrufe != 2 {
|
||||
t.Fatalf("zwei Orte, %d Anfragen", aufrufe)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWettercodesWerdenZuSymbolen(t *testing.T) {
|
||||
fälle := map[int]string{
|
||||
0: "sonne", 2: "heiter", 3: "wolke", 45: "nebel",
|
||||
61: "regen", 80: "regen", 73: "schnee", 86: "schnee", 95: "gewitter",
|
||||
}
|
||||
for code, will := range fälle {
|
||||
if g, _ := wmo(code); g != will {
|
||||
t.Errorf("Code %d → %q, erwartet %q", code, g, will)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrtssucheGibtKoordinatenZurueck(t *testing.T) {
|
||||
d := fakeWetter(t, 0, 20, nil)
|
||||
orte, err := d.Suche(context.Background(), "Köln")
|
||||
if err != nil {
|
||||
t.Fatalf("unerwartet: %v", err)
|
||||
}
|
||||
if len(orte) != 1 || orte[0].Lat == 0 || orte[0].Lon == 0 {
|
||||
t.Fatalf("ohne Koordinaten müsste jede Abfrage neu suchen: %+v", orte)
|
||||
}
|
||||
// Bundesland statt Land: „Köln, Deutschland" gibt es mehrfach.
|
||||
if !strings.Contains(orte[0].Land, "Nordrhein-Westfalen") {
|
||||
t.Fatalf("Land zu grob: %q", orte[0].Land)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSucheOhneBegriffFragtNichtAn(t *testing.T) {
|
||||
var aufrufe int32
|
||||
d := fakeWetter(t, 0, 20, &aufrufe)
|
||||
if _, err := d.Suche(context.Background(), " "); err == nil {
|
||||
t.Fatal("leerer Begriff müsste abgelehnt werden")
|
||||
}
|
||||
if aufrufe != 0 {
|
||||
t.Fatalf("leerer Begriff ging trotzdem nach draußen (%d)", aufrufe)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user