package render import ( "strings" "testing" flatbuffers "github.com/google/flatbuffers/go" "scrabble/pkg/fbs/scrabblefb" ) const gameID = "7c9e6679-7425-40de-944b-e07fc1f90ae7" func yourTurnPayload(id string) []byte { b := flatbuffers.NewBuilder(0) gid := b.CreateString(id) scrabblefb.YourTurnEventStart(b) scrabblefb.YourTurnEventAddGameId(b, gid) b.Finish(scrabblefb.YourTurnEventEnd(b)) return b.FinishedBytes() } func nudgePayload(id string) []byte { b := flatbuffers.NewBuilder(0) gid := b.CreateString(id) scrabblefb.NudgeEventStart(b) scrabblefb.NudgeEventAddGameId(b, gid) b.Finish(scrabblefb.NudgeEventEnd(b)) return b.FinishedBytes() } func matchFoundPayload(id string) []byte { b := flatbuffers.NewBuilder(0) gid := b.CreateString(id) scrabblefb.MatchFoundEventStart(b) scrabblefb.MatchFoundEventAddGameId(b, gid) b.Finish(scrabblefb.MatchFoundEventEnd(b)) return b.FinishedBytes() } func notifyPayload(kind string) []byte { b := flatbuffers.NewBuilder(0) k := b.CreateString(kind) scrabblefb.NotificationEventStart(b) scrabblefb.NotificationEventAddKind(b, k) b.Finish(scrabblefb.NotificationEventEnd(b)) return b.FinishedBytes() } func enrichedYourTurnPayload(id, name, action, word, score string) []byte { b := flatbuffers.NewBuilder(0) gid := b.CreateString(id) n := b.CreateString(name) a := b.CreateString(action) w := b.CreateString(word) s := b.CreateString(score) scrabblefb.YourTurnEventStart(b) scrabblefb.YourTurnEventAddGameId(b, gid) scrabblefb.YourTurnEventAddOpponentName(b, n) scrabblefb.YourTurnEventAddLastAction(b, a) scrabblefb.YourTurnEventAddLastWord(b, w) scrabblefb.YourTurnEventAddScoreLine(b, s) b.Finish(scrabblefb.YourTurnEventEnd(b)) return b.FinishedBytes() } func gameOverPayload(id, result, score string) []byte { b := flatbuffers.NewBuilder(0) gid := b.CreateString(id) r := b.CreateString(result) s := b.CreateString(score) scrabblefb.GameOverEventStart(b) scrabblefb.GameOverEventAddGameId(b, gid) scrabblefb.GameOverEventAddResult(b, r) scrabblefb.GameOverEventAddScoreLine(b, s) b.Finish(scrabblefb.GameOverEventEnd(b)) return b.FinishedBytes() } // TestRenderYourTurnEnriched checks the enriched "your turn" body names the opponent, their word // and the recipient-first score for a scoring play, and uses the shorter phrase for an exchange. func TestRenderYourTurnEnriched(t *testing.T) { en, ok := Render("your_turn", enrichedYourTurnPayload(gameID, "Ann", "play", "STOOL", "120:95"), "en") if !ok { t.Fatal("expected ok") } for _, want := range []string{"Ann", "STOOL", "120:95"} { if !strings.Contains(en.Text, want) { t.Errorf("en play text %q missing %q", en.Text, want) } } ru, _ := Render("your_turn", enrichedYourTurnPayload(gameID, "Аня", "play", "СТОЛ", "120:95"), "ru") for _, want := range []string{"Аня", "СТОЛ", "120:95", "мой ход"} { if !strings.Contains(ru.Text, want) { t.Errorf("ru play text %q missing %q", ru.Text, want) } } ex, _ := Render("your_turn", enrichedYourTurnPayload(gameID, "Ann", "exchange", "", ""), "en") if !strings.Contains(ex.Text, "Ann") || strings.Contains(ex.Text, "«") { t.Errorf("en exchange text %q should name the opponent and carry no word", ex.Text) } } // TestRenderGameOver checks the game_over body reads from the recipient's perspective in both // languages and carries the game deep-link. func TestRenderGameOver(t *testing.T) { cases := []struct{ result, en, ru string }{ {"won", "won", "выиграли"}, {"lost", "lost", "проиграли"}, {"draw", "draw", "ничья"}, } for _, tc := range cases { en, ok := Render("game_over", gameOverPayload(gameID, tc.result, "120:95"), "en") if !ok { t.Fatalf("%s: expected ok", tc.result) } if en.StartParam != "g"+gameID { t.Errorf("%s StartParam = %q, want g%s", tc.result, en.StartParam, gameID) } if !strings.Contains(strings.ToLower(en.Text), tc.en) || !strings.Contains(en.Text, "120:95") { t.Errorf("en %s text %q", tc.result, en.Text) } ru, _ := Render("game_over", gameOverPayload(gameID, tc.result, "120:95"), "ru") if !strings.Contains(ru.Text, tc.ru) { t.Errorf("ru %s text %q missing %q", tc.result, ru.Text, tc.ru) } } } func TestRenderGameEvents(t *testing.T) { cases := []struct { name, kind string payload []byte }{ {"your_turn", "your_turn", yourTurnPayload(gameID)}, {"nudge", "nudge", nudgePayload(gameID)}, {"match_found", "match_found", matchFoundPayload(gameID)}, } for _, tc := range cases { t.Run(tc.name+" en", func(t *testing.T) { m, ok := Render(tc.kind, tc.payload, "en") if !ok { t.Fatal("expected ok") } if m.StartParam != "g"+gameID { t.Errorf("StartParam = %q, want g%s", m.StartParam, gameID) } if m.ButtonText != "Open game" { t.Errorf("ButtonText = %q, want Open game", m.ButtonText) } if m.Text == "" { t.Error("Text is empty") } }) t.Run(tc.name+" ru", func(t *testing.T) { m, ok := Render(tc.kind, tc.payload, "ru") if !ok { t.Fatal("expected ok") } if m.ButtonText != "Открыть игру" { t.Errorf("ButtonText = %q, want Открыть игру", m.ButtonText) } }) } } func TestRenderNotify(t *testing.T) { cases := map[string]struct { subKind string wantOK bool }{ "invitation": {"invitation", true}, "friend_request": {"friend_request", true}, "friend_added": {"friend_added", false}, "game_started": {"game_started", false}, } for name, tc := range cases { t.Run(name, func(t *testing.T) { m, ok := Render("notify", notifyPayload(tc.subKind), "en") if ok != tc.wantOK { t.Fatalf("ok = %v, want %v", ok, tc.wantOK) } if ok && m.StartParam != "" { t.Errorf("StartParam = %q, want empty (lobby)", m.StartParam) } }) } } func TestRenderSkipsUnpushedKinds(t *testing.T) { for _, kind := range []string{"opponent_moved", "chat_message", "unknown"} { if _, ok := Render(kind, nil, "en"); ok { t.Errorf("kind %q: ok = true, want false", kind) } } }