โ– humdrum codex / sportsball v0.1.0
license AGPL-3.0
3.3 KB raw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package espn

import (
	"testing"

	"github.com/humdrum-tiv/sportsball/internal/model"
)

// mapStandings must flatten nested children (conference โ†’ entries) into one
// group per table, select the per-sport columns, and rank by entry order.
func TestMapStandingsNestedAndColumns(t *testing.T) {
	root := standingsNode{
		Name: "National Basketball Association",
		Children: []standingsNode{
			{
				Name: "Eastern Conference",
				Standings: standingsBlock{Entries: []standingsEntry{
					{
						Team:  teamJSON{ID: "1", Abbreviation: "BOS", ShortDisplayName: "Celtics"},
						Stats: []standingStat{{Type: "wins", DisplayValue: "50"}, {Type: "losses", DisplayValue: "20"}, {Type: "winpercent", DisplayValue: ".714"}, {Type: "gamesbehind", DisplayValue: "-"}},
					},
					{
						Team:  teamJSON{ID: "2", Abbreviation: "NYK", ShortDisplayName: "Knicks"},
						Stats: []standingStat{{Type: "wins", DisplayValue: "45"}, {Type: "losses", DisplayValue: "25"}},
					},
				}},
			},
		},
	}

	s := mapStandings(model.NBA, "basketball", root)
	if len(s.Groups) != 1 {
		t.Fatalf("want 1 group, got %d", len(s.Groups))
	}
	g := s.Groups[0]
	if g.Name != "Eastern Conference" {
		t.Errorf("group name = %q", g.Name)
	}
	wantCols := []string{"W", "L", "PCT", "GB"}
	if len(g.Columns) != len(wantCols) {
		t.Fatalf("columns = %v", g.Columns)
	}
	for i, c := range wantCols {
		if g.Columns[i] != c {
			t.Errorf("col %d = %q, want %q", i, g.Columns[i], c)
		}
	}
	if g.Rows[0].Rank != 1 || g.Rows[0].Team.Abbr != "BOS" {
		t.Errorf("row0 = %+v", g.Rows[0])
	}
	if got := g.Rows[0].Values; got[0] != "50" || got[1] != "20" || got[2] != ".714" || got[3] != "-" {
		t.Errorf("row0 values = %v", got)
	}
	// Missing stats map to empty strings, not a panic.
	if got := g.Rows[1].Values; got[2] != "" || got[3] != "" {
		t.Errorf("row1 missing stats should be blank: %v", got)
	}
}

// ESPN returns entries unsorted; mapStandings must order each group by the
// sport's rank stat (basketball = playoffseed) before assigning ranks.
func TestMapStandingsSortsByRankStat(t *testing.T) {
	root := standingsNode{Standings: standingsBlock{Entries: []standingsEntry{
		{Team: teamJSON{Abbreviation: "ATL"}, Stats: []standingStat{{Type: "playoffseed", DisplayValue: "6"}, {Type: "wins", DisplayValue: "46"}}},
		{Team: teamJSON{Abbreviation: "DET"}, Stats: []standingStat{{Type: "playoffseed", DisplayValue: "1"}, {Type: "wins", DisplayValue: "60"}}},
		{Team: teamJSON{Abbreviation: "BOS"}, Stats: []standingStat{{Type: "playoffseed", DisplayValue: "2"}, {Type: "wins", DisplayValue: "56"}}},
	}}}
	g := mapStandings(model.NBA, "basketball", root).Groups[0]
	want := []string{"DET", "BOS", "ATL"}
	for i, abbr := range want {
		if g.Rows[i].Team.Abbr != abbr || g.Rows[i].Rank != i+1 {
			t.Errorf("row %d = %s rank %d, want %s rank %d", i, g.Rows[i].Team.Abbr, g.Rows[i].Rank, abbr, i+1)
		}
	}
}

func TestStandingsRowAt(t *testing.T) {
	s := model.Standings{Groups: []model.StandingsGroup{
		{Rows: []model.StandingsRow{{Team: model.Team{Abbr: "A"}}, {Team: model.Team{Abbr: "B"}}}},
		{Rows: []model.StandingsRow{{Team: model.Team{Abbr: "C"}}}},
	}}
	if s.RowCount() != 3 {
		t.Fatalf("RowCount = %d", s.RowCount())
	}
	if r, ok := s.RowAt(2); !ok || r.Team.Abbr != "C" {
		t.Errorf("RowAt(2) = %+v ok=%v, want C", r, ok)
	}
	if _, ok := s.RowAt(3); ok {
		t.Error("RowAt(3) should be out of range")
	}
}