package cli import ( "net/http" "net/http/httptest" "testing" "github.com/gdamore/tcell/v2" api "sneak.berlin/go/neoirc/internal/cli/api" "sneak.berlin/go/neoirc/pkg/irc" ) // The user's nick, a channel it is in, another user's nick and // a message text, for the tests below. const ( testNick = "erin" testChannel = "#chan" testOtherNick = "bob" testText = "hello" ) func TestMemberPrefix(t *testing.T) { t.Parallel() tests := []struct { name string member api.Member want string }{ { name: "operator", member: api.Member{Nick: "alice", IsOperator: true}, want: "@", }, { name: "voiced member", member: api.Member{Nick: "bob", IsVoiced: true}, want: "+", }, { name: "voiced operator", member: api.Member{ Nick: "Carol", IsOperator: true, IsVoiced: true, }, want: "@", }, { name: "plain member", member: api.Member{Nick: "dave"}, want: "", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() got := memberPrefix(test.member) if got != test.want { t.Fatalf("got %q, want %q", got, test.want) } }) } } // TestPartEvent checks that a PART event adds a line to the // channel's buffer and marks it unread, except that the // user's own part does not mark it unread. func TestPartEvent(t *testing.T) { t.Parallel() tests := []struct { name string from string wantUnread int }{ {name: "own part", from: testNick, wantUnread: 0}, {name: "other member", from: testOtherNick, wantUnread: 1}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() app := &App{ui: NewUI(), nick: testNick} runUI(t, app.ui) app.handleServerMessage(&api.Message{ Command: irc.CmdPart, From: test.from, To: testChannel, }) lines, unread := bufferState(app.ui, testChannel) if lines != 1 || unread != test.wantUnread { t.Fatalf("got %d lines and %d unread, want 1 and %d", lines, unread, test.wantUnread) } }) } } // TestOwnCommandShownOnce checks that a message, join or // part made from neoirc-cli shows as one line in the // channel's buffer once the server's event for it arrives, // and does not mark the channel unread. func TestOwnCommandShownOnce(t *testing.T) { t.Parallel() tests := []struct { name string send func(app *App) event api.Message }{ { name: "message", send: func(app *App) { app.handleInput(testText) }, event: api.Message{ Command: irc.CmdPrivmsg, From: testNick, To: testChannel, Body: []string{testText}, }, }, { name: "join", send: func(app *App) { app.cmdJoin(testChannel) }, event: api.Message{ Command: irc.CmdJoin, From: testNick, To: testChannel, }, }, { name: "part", send: func(app *App) { app.cmdPart(testChannel) }, event: api.Message{ Command: irc.CmdPart, From: testNick, To: testChannel, }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() // The server accepts every command and answers nothing. server := httptest.NewServer(http.HandlerFunc( func(http.ResponseWriter, *http.Request) {}, )) t.Cleanup(server.Close) app := &App{ ui: NewUI(), client: api.NewClient(server.URL), nick: testNick, target: testChannel, connected: true, } runUI(t, app.ui) app.ui.SwitchToBuffer(testChannel) test.send(app) app.handleServerMessage(&test.event) lines, unread := bufferState(app.ui, testChannel) if lines != 1 || unread != 0 { t.Fatalf("got %d lines and %d unread, want 1 and 0", lines, unread) } }) } } // TestOwnEventFromOtherClient checks that a message, notice, // join or part that the user's session makes from another // client shows as one line in the buffer it belongs to. func TestOwnEventFromOtherClient(t *testing.T) { t.Parallel() tests := []struct { name string event api.Message buffer string }{ { name: "channel message", event: api.Message{ Command: irc.CmdPrivmsg, From: testNick, To: testChannel, Body: []string{testText}, }, buffer: testChannel, }, { name: "direct message", event: api.Message{ Command: irc.CmdPrivmsg, From: testNick, To: testOtherNick, Body: []string{testText}, }, buffer: testOtherNick, }, { name: "notice", event: api.Message{ Command: irc.CmdNotice, From: testNick, To: testChannel, Body: []string{testText}, }, buffer: "(status)", }, { name: "join", event: api.Message{ Command: irc.CmdJoin, From: testNick, To: testChannel, }, buffer: testChannel, }, { name: "part", event: api.Message{ Command: irc.CmdPart, From: testNick, To: testChannel, }, buffer: testChannel, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() app := &App{ui: NewUI(), nick: testNick} runUI(t, app.ui) app.handleServerMessage(&test.event) lines, _ := bufferState(app.ui, test.buffer) if lines != 1 { t.Fatalf("got %d lines in %s, want 1", lines, test.buffer) } }) } } // bufferState returns the number of lines in the named // buffer and how many of them are unread. func bufferState(ui *UI, name string) (int, int) { var lines, unread int ui.app.QueueUpdate(func() { buf := ui.getOrCreateBuffer(name) lines = len(buf.Lines) unread = buf.Unread }) return lines, unread } // runUI runs ui on a simulated screen until the test ends. func runUI(t *testing.T, ui *UI) { t.Helper() ui.app.SetScreen(tcell.NewSimulationScreen("")) done := make(chan error) go func() { done <- ui.Run() }() t.Cleanup(func() { ui.Stop() err := <-done if err != nil { t.Errorf("run ui: %v", err) } }) }