Show the neoirc-cli screen without waiting at start-up (closes #226)
check / check (push) Waiting to run
check / check (push) Waiting to run
Run added the start-up status bar and welcome lines through SetStatus and AddStatus before it started the tview event loop. Both wait for that loop, so neoirc-cli stopped before it showed its screen. They now run on a goroutine, as typed lines do. Run now passes a new UI to run, which holds the start-up path and returns its error. TestStartUp calls run with a simulation screen. It fails unless, within five seconds, the status window holds two lines: first the one with "Welcome to neoirc-cli", then the /connect hint. Model: opus-5-5
This commit was merged in pull request #230.
This commit is contained in:
+32
-18
@@ -34,30 +34,44 @@ type App struct {
|
||||
|
||||
// Run creates and runs the CLI application.
|
||||
func Run() {
|
||||
app := &App{ //nolint:exhaustruct
|
||||
ui: NewUI(),
|
||||
nick: "guest",
|
||||
}
|
||||
|
||||
app.ui.OnInput(app.handleInput)
|
||||
app.ui.SetStatus(app.nick, "", "disconnected")
|
||||
|
||||
app.ui.AddStatus(
|
||||
"Welcome to neoirc-cli — an IRC-style client",
|
||||
)
|
||||
app.ui.AddStatus(
|
||||
"Type [yellow]/connect <server-url>" +
|
||||
"[white] to begin, " +
|
||||
"or [yellow]/help[white] for commands",
|
||||
)
|
||||
|
||||
err := app.ui.Run()
|
||||
err := run(NewUI())
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// run starts the client on ui and returns when ui stops. Tests
|
||||
// call it with ui on a simulation screen.
|
||||
func run(ui *UI) error {
|
||||
app := &App{ //nolint:exhaustruct
|
||||
ui: ui,
|
||||
nick: "guest",
|
||||
}
|
||||
|
||||
app.ui.OnInput(app.handleInput)
|
||||
|
||||
// SetStatus and AddStatus wait for the event loop, which
|
||||
// app.ui.Run starts below, so they run on a goroutine. The
|
||||
// nick is read first, as a typed /nick may change app.nick.
|
||||
nick := app.nick
|
||||
|
||||
go func() {
|
||||
app.ui.SetStatus(nick, "", "disconnected")
|
||||
|
||||
app.ui.AddStatus(
|
||||
"Welcome to neoirc-cli — an IRC-style client",
|
||||
)
|
||||
app.ui.AddStatus(
|
||||
"Type [yellow]/connect <server-url>" +
|
||||
"[white] to begin, " +
|
||||
"or [yellow]/help[white] for commands",
|
||||
)
|
||||
}()
|
||||
|
||||
return app.ui.Run()
|
||||
}
|
||||
|
||||
func (a *App) handleInput(text string) {
|
||||
if strings.HasPrefix(text, "/") {
|
||||
a.handleCommand(text)
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -22,13 +24,22 @@ const (
|
||||
)
|
||||
|
||||
// eventLoopTimeout is how long a test waits for a typed line to
|
||||
// be handled, or for the UI to stop, before it fails.
|
||||
// be handled, for the welcome lines, or for the UI to stop, before
|
||||
// it fails.
|
||||
const eventLoopTimeout = 5 * time.Second
|
||||
|
||||
// holdTime is how long TestLinesKeepOrder holds the first line,
|
||||
// which gives the second line time to start too early.
|
||||
const holdTime = 200 * time.Millisecond
|
||||
|
||||
// welcomeLines is the number of lines run adds to the status
|
||||
// window when the client starts.
|
||||
const welcomeLines = 2
|
||||
|
||||
// checkInterval is how often TestStartUp reads the status window
|
||||
// while it waits for the welcome lines.
|
||||
const checkInterval = 10 * time.Millisecond
|
||||
|
||||
func TestMemberPrefix(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -348,6 +359,78 @@ func TestLinesKeepOrder(t *testing.T) {
|
||||
waitHandled(t, handled, secondLine)
|
||||
}
|
||||
|
||||
// TestStartUp runs the client's start-up path on a simulation
|
||||
// screen. It fails unless the event loop starts and the status
|
||||
// window gets two lines: first the one that holds "Welcome to
|
||||
// neoirc-cli", then the /connect hint.
|
||||
func TestStartUp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ui := NewUI()
|
||||
ui.app.SetScreen(tcell.NewSimulationScreen(""))
|
||||
|
||||
// One slot, so that run's goroutine can end even after the
|
||||
// cleanup below has stopped waiting for it.
|
||||
done := make(chan error, 1)
|
||||
|
||||
go func() { done <- run(ui) }()
|
||||
|
||||
t.Cleanup(func() {
|
||||
ui.Stop()
|
||||
|
||||
select {
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
t.Errorf("run: %v", err)
|
||||
}
|
||||
case <-time.After(eventLoopTimeout):
|
||||
t.Error("run did not return after the UI was stopped")
|
||||
}
|
||||
})
|
||||
|
||||
// run adds the welcome lines on a goroutine, so the status
|
||||
// window is read until it holds two lines. Each read waits for
|
||||
// the event loop, so the reads run on a goroutine too, which
|
||||
// this test stops waiting for after eventLoopTimeout. The
|
||||
// channel has one slot, so that goroutine can still end.
|
||||
shown := make(chan []string, 1)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
var lines []string
|
||||
|
||||
ui.app.QueueUpdate(func() {
|
||||
lines = slices.Clone(ui.getOrCreateBuffer("(status)").Lines)
|
||||
})
|
||||
|
||||
if len(lines) == welcomeLines {
|
||||
shown <- lines
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(checkInterval)
|
||||
}
|
||||
}()
|
||||
|
||||
var lines []string
|
||||
|
||||
select {
|
||||
case lines = <-shown:
|
||||
case <-time.After(eventLoopTimeout):
|
||||
t.Fatalf("the status window did not get the welcome lines within %v",
|
||||
eventLoopTimeout)
|
||||
}
|
||||
|
||||
if !strings.Contains(lines[0], "Welcome to neoirc-cli") {
|
||||
t.Errorf("first status line is %q, want the welcome line", lines[0])
|
||||
}
|
||||
|
||||
if !strings.Contains(lines[1], "/connect <server-url>") {
|
||||
t.Errorf("second status line is %q, want the /connect hint", lines[1])
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
Reference in New Issue
Block a user