aboutsummaryrefslogtreecommitdiff
path: root/internal/game/prediction_ux_regression_test.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-10-05 06:03:49 -0400
committerhistoria <[not public]>2026-10-05 06:03:49 -0400
commitc84a9944356f590d90d819d652bdf1349ebacd40 (patch)
treeaf1ee15992e141fb3eb6f26cac02cf06306d4982 /internal/game/prediction_ux_regression_test.go
parent8b78aa4cd930fde145959704ba7bdc0e57465b9b (diff)
downloadthehouseoficarus-c84a9944356f590d90d819d652bdf1349ebacd40.tar.gz
feat: prediction exchange with live polymarket data
Diffstat (limited to 'internal/game/prediction_ux_regression_test.go')
-rw-r--r--internal/game/prediction_ux_regression_test.go171
1 files changed, 171 insertions, 0 deletions
diff --git a/internal/game/prediction_ux_regression_test.go b/internal/game/prediction_ux_regression_test.go
new file mode 100644
index 0000000..159a492
--- /dev/null
+++ b/internal/game/prediction_ux_regression_test.go
@@ -0,0 +1,171 @@
+package game
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "thehouseoficarus/internal/color"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/prediction"
+ "thehouseoficarus/internal/world"
+)
+
+func TestPredictionOneComplementaryBarPerMarket(t *testing.T) {
+ for _, unicode := range []bool{true, false} {
+ t.Run(fmt.Sprint(unicode), func(t *testing.T) {
+ g, s, conn, m := predictionFixture(t)
+ s.Player.Options["unicode"] = unicode
+ for _, detail := range []bool{false, true} {
+ conn.data = nil
+ if detail {
+ g.predictionView(s, m, nil)
+ } else {
+ g.predictionList(s, []prediction.Market{m}, 1, "")
+ }
+ output := string(conn.data)
+ bars := 0
+ for _, line := range strings.Split(output, "\n") {
+ if strings.Contains(line, "█") || strings.Contains(line, "#") && strings.Contains(line, ".") {
+ bars++
+ }
+ }
+ if bars != 1 {
+ t.Fatalf("detail=%v: got %d bars, want one\n%s", detail, bars, output)
+ }
+ if !strings.Contains(output, "YES") || !strings.Contains(output, "42.1%") || !strings.Contains(output, "57.9%") || !strings.Contains(output, "NO") {
+ t.Fatal("missing two-sided odds labels")
+ }
+ if strings.Contains(output, m.Description) || strings.Contains(output, "RESOLUTION RULES") {
+ t.Fatal("resolution text shown by default")
+ }
+ if detail {
+ for _, quote := range []string{"CHOOSE YOUR OUTCOME", "Outcome", "Buy", "Sell", "43", "42", "58", "57"} {
+ if !strings.Contains(output, quote) {
+ t.Fatalf("missing integer quote %q", quote)
+ }
+ }
+ }
+ }
+ })
+ }
+}
+
+func TestPredictionRulesFramePreservesTextWithoutCarriageReturns(t *testing.T) {
+ for _, width := range []int{40, 80} {
+ t.Run(fmt.Sprint(width), func(t *testing.T) {
+ g, s, conn, m := predictionFixture(t)
+ s.Player.Options["wrap_width"] = width
+ url := "https://example.org/" + strings.Repeat("long-resolution-source", 10)
+ m.Description = strings.Repeat("This market resolves according to the party controlling the chamber. ", 12) + "\r\n\r\n" + url
+ predictionTestServer(t, g, m)
+ g.doUse(s, "market")
+ g.handleGameCommand(s, "1")
+ if strings.Contains(string(conn.data), "RESOLUTION RULES") {
+ t.Fatal("rules appeared before request")
+ }
+ conn.data = nil
+ g.handleGameCommand(s, "rules")
+ text := strings.ReplaceAll(string(conn.data), "\r\n", "\n")
+ if strings.Contains(text, "\r") {
+ t.Fatal("bare CR inside frame can overwrite text")
+ }
+ var body strings.Builder
+ for _, line := range strings.Split(text, "\n") {
+ if strings.HasPrefix(line, "║") {
+ if !strings.HasSuffix(line, "║") {
+ t.Fatalf("missing right edge: %q", line)
+ }
+ if color.VisibleLen(line) != width {
+ t.Fatalf("row width %d, want %d: %q", color.VisibleLen(line), width, line)
+ }
+ body.WriteString(strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "║"), "║")))
+ }
+ }
+ if !strings.Contains(text, "RESOLUTION RULES") || !strings.Contains(body.String(), url) {
+ t.Fatal("rules or unbroken URL was lost during wrapping")
+ }
+ if s.PredictionScreen != "view" || s.PredictionSelected != m.ID {
+ t.Fatal("reading rules lost selected market")
+ }
+ })
+ }
+}
+
+func TestPredictionLookUsesCasinoMenuAndRoomFirstOrder(t *testing.T) {
+ g, s, conn, m := predictionFixture(t)
+ g.Hub = net.NewHub()
+ g.MobStore = world.NewMobStore(t.TempDir())
+ s.Player.Options["tiny_map"] = "off"
+ s.Player.Options["casino_hints"] = "once"
+ predictionTestServer(t, g, m)
+ g.doUse(s, "counter")
+ g.handleGameCommand(s, "1")
+ conn.data = nil
+ g.handleGameCommand(s, "look")
+ text := strings.ReplaceAll(string(conn.data), "\r\n", "\n")
+ room, screen := strings.Index(text, "Test Counter"), strings.Index(text, "THE MARKET")
+ if room < 0 || screen < room {
+ t.Fatalf("look not room first: %s", text)
+ }
+ expected := g.renderCasinoCommands(s, "Available 10000 credits", "", g.predictionCommands(s))
+ if !strings.Contains(text, expected) {
+ t.Fatalf("look did not use the shared casino menu\nwant: %s\ngot: %s", expected, text)
+ }
+ for _, unwanted := range []string{"YOUR NEXT MOVE", "what -", "what /", "RESOLUTION RULES"} {
+ if strings.Contains(text, unwanted) {
+ t.Fatalf("look leaked %q", unwanted)
+ }
+ }
+ conn.data = nil
+ g.handleGameCommand(s, "what")
+ text = strings.ReplaceAll(string(conn.data), "\r\n", "\n")
+ if !strings.Contains(text, g.casinoMenuCommands(s, g.predictionCommands(s))) {
+ t.Fatal("normal what did not use casino hint renderer")
+ }
+ if g.predictionInputOwned(s, "look") || g.predictionInputOwned(s, "what") {
+ t.Fatal("counter hijacked normal world command")
+ }
+ conn.data = nil
+ s.Player.Options["casino_hints"] = "off"
+ g.handleGameCommand(s, "look")
+ if strings.Contains(string(conn.data), "view N") {
+ t.Fatal("look ignored casino_hints off")
+ }
+}
+
+func TestPredictionFrameLongColoredToken(t *testing.T) {
+ g, s, _, _ := predictionFixture(t)
+ s.Player.Options["color"] = "xterm256"
+ token := g.colorize(s, "casino_action", strings.Repeat("x", 160))
+ lines := predictionFrameLines(token, 36)
+ for _, line := range lines {
+ if strings.Contains(line, "\r") || color.VisibleLen(line) > 36 {
+ t.Fatalf("unsafe wrapped row %q", line)
+ }
+ }
+}
+
+func TestPredictionOddsBarsAlign(t *testing.T) {
+ g, s, _, _ := predictionFixture(t)
+ s.Player.Options["color"] = "none"
+ target := 36
+ pairs := [][2]string{{"Yes", "No"}, {"M80", "TYLOO"}, {"9z", "Natus Vincere"}, {"Falcons", "Saints"}}
+ cells := make([]string, 0, len(pairs))
+ for _, pair := range pairs {
+ m := prediction.Market{ID: "1", Outcomes: `["` + pair[0] + `","` + pair[1] + `"]`, Tokens: `["a","b"]`, Prices: `["0.421","0.579"]`}
+ cells = append(cells, g.predictionOddsCell(s, m, target))
+ }
+ start := strings.Index(cells[0], "█")
+ if start < 0 {
+ t.Fatalf("no bar: %q", cells[0])
+ }
+ for _, cell := range cells {
+ if got := strings.Index(cell, "█"); got != start {
+ t.Fatalf("bar offset %d, want %d: %q", got, start, cell)
+ }
+ if color.VisibleLen(cell) != target {
+ t.Fatalf("cell width %d, want %d: %q", color.VisibleLen(cell), target, cell)
+ }
+ }
+}