]> git.giacomolli.uk - onechat/blobdiff - terminal_test.go
Send stored images to WhatsApp chats
[onechat] / terminal_test.go
index 91e43d2d84812e966eb8a35d00353449d739e33b..7b68edb4ed389ceb09355c40c02b85d3d62a5376 100644 (file)
@@ -1,13 +1,16 @@
 package main
 
 import (
+       "encoding/json"
        "io"
        "net"
+       "os"
        "path/filepath"
        "strings"
        "testing"
 
        "github.com/stretchr/testify/require"
+       "golang.org/x/sys/unix"
 )
 
 func TestClientEndsWhenTheDaemonEnds(t *testing.T) {
@@ -58,6 +61,16 @@ func TestEventText(t *testing.T) {
                        event: Event{Kind: "user", Text: "from the phone", Source: "telegram"},
                        text:  "[telegram] from the phone",
                },
+               {
+                       name:  "start of a subagent",
+                       event: Event{Kind: "subagent", Text: "GreenOtter"},
+                       text:  "[GreenOtter] started",
+               },
+               {
+                       name:  "report of a subagent whole",
+                       event: Event{Kind: "work", Text: "[GreenOtter]\nThere are 3 files.\n\nFull chat: zoom(\"GreenOtter\")", Source: "GreenOtter", Status: subagentFinished},
+                       text:  "[GreenOtter]\nThere are 3 files.\n\nFull chat: zoom(\"GreenOtter\")",
+               },
                {
                        name:  "long result cut",
                        event: Event{Kind: "echo", Text: strings.Repeat("line\n", 12)},
@@ -71,3 +84,226 @@ func TestEventText(t *testing.T) {
                })
        }
 }
+
+func TestTerminalEventsKeepMarkdownInNonterminalOutput(t *testing.T) {
+       var out strings.Builder
+
+       text := "# Heading\n\n**bold** and `code`\nA | B\n--- | ---\nx | y"
+       terminalTestShowEvents(t, &out, Event{
+               Kind: "reply",
+               Text: text,
+       }, Event{
+               Kind: "work",
+               Text: "[BlueBird]\n" + text,
+       })
+       require.Equal(t, text+"\n[BlueBird]\n"+text+"\n", out.String())
+}
+
+func TestTerminalFileOutputKeepsMarkdown(t *testing.T) {
+       out, err := os.CreateTemp(t.TempDir(), "output")
+       require.NoError(t, err)
+
+       defer func() {
+               require.NoError(t, out.Close())
+       }()
+
+       text := "**plain**\n---\n`code`"
+       terminalTestShowEvents(t, out, Event{
+               Kind: "reply",
+               Text: text,
+       })
+       require.Zero(t, terminalColumns(out))
+
+       contents, err := os.ReadFile(out.Name())
+       require.NoError(t, err)
+       require.Equal(t, text+"\n", string(contents))
+}
+
+func TestTerminalStylesOnlyRepliesAndWork(t *testing.T) {
+       fd, err := unix.Open("/dev/ptmx", unix.O_RDWR|unix.O_NOCTTY, 0)
+       require.NoError(t, err)
+
+       defer func() {
+               require.NoError(t, unix.Close(fd))
+       }()
+
+       require.NoError(t, unix.IoctlSetWinsize(fd, unix.TIOCSWINSZ, &unix.Winsize{Col: 12}))
+
+       out := TerminalTestOutput{fd: fd}
+       require.Equal(t, 12, terminalColumns(&out))
+       terminalTestShowEvents(t, &out,
+               Event{Kind: "busy"},
+               Event{
+                       Kind: "reply",
+                       Text: "**first next last**",
+               },
+               Event{
+                       Kind: "work",
+                       Text: "[BlueBird]\n# Report",
+               },
+               Event{
+                       Kind: "tool",
+                       Text: "**raw tool**",
+               },
+               Event{
+                       Kind: "echo",
+                       Text: "**raw result**",
+               },
+               Event{
+                       Kind: "error",
+                       Text: "**raw error**",
+               },
+               Event{
+                       Kind: "notice",
+                       Text: "**raw notice**",
+               },
+               Event{
+                       Kind:   "user",
+                       Text:   "**raw input**",
+                       Source: "telegram",
+               },
+               Event{
+                       Kind: "ready",
+                       Text: "**raw agent**",
+               },
+               Event{
+                       Kind: "subagent",
+                       Text: "BlueBird",
+               },
+               Event{Kind: "busy"},
+               Event{Kind: "idle"},
+       )
+       require.Equal(t, "[working]\n\x1b[1mfirst next\x1b[0m\n\x1b[1mlast\x1b[0m\n[BlueBird]\n\x1b[1mReport\x1b[0m\n"+
+               "[tool] **raw tool**\n[result] **raw result**\n[error] **raw error**\n**raw notice**\n[telegram] **raw input**\n"+
+               "[connected to **raw agent**]\n[BlueBird] started\n[working]\n[done]\n> ", out.String())
+}
+
+func TestTerminalZeroWidthUsesEightyAndReadsChanges(t *testing.T) {
+       fd, err := unix.Open("/dev/ptmx", unix.O_RDWR|unix.O_NOCTTY, 0)
+       require.NoError(t, err)
+
+       defer func() {
+               require.NoError(t, unix.Close(fd))
+       }()
+
+       out := TerminalTestOutput{fd: fd}
+       require.NoError(t, unix.IoctlSetWinsize(fd, unix.TIOCSWINSZ, &unix.Winsize{Col: 0}))
+       require.Equal(t, 80, terminalColumns(&out))
+       terminalTestShowEvents(t, &out, Event{
+               Kind: "reply",
+               Text: strings.Repeat("a", 81),
+       })
+       require.Equal(t, strings.Repeat("a", 80)+"\na\n> ", out.String())
+       require.NoError(t, unix.IoctlSetWinsize(fd, unix.TIOCSWINSZ, &unix.Winsize{Col: 40}))
+       require.Equal(t, 40, terminalColumns(&out))
+}
+
+func TestTerminalPromptsAfterIdleEventsAndClearsBeforeShownEvents(t *testing.T) {
+       fd, err := unix.Open("/dev/ptmx", unix.O_RDWR|unix.O_NOCTTY, 0)
+       require.NoError(t, err)
+
+       defer func() {
+               require.NoError(t, unix.Close(fd))
+       }()
+
+       out := TerminalTestOutput{fd: fd}
+       terminalTestShowEvents(t, &out,
+               Event{
+                       Kind: "ready",
+                       Text: "Example",
+               },
+               Event{
+                       Kind:   "user",
+                       Source: terminalSource,
+                       Text:   "hidden",
+               },
+               Event{Kind: "unknown"},
+               Event{
+                       Kind: "notice",
+                       Text: "Idle notice",
+               },
+               Event{
+                       Kind: "work",
+                       Text: "**Idle report**",
+               },
+               Event{Kind: "busy"},
+               Event{
+                       Kind: "notice",
+                       Text: "Busy notice",
+               },
+               Event{
+                       Kind: "reply",
+                       Text: "**Answer**",
+               },
+               Event{Kind: "idle"},
+               Event{
+                       Kind: "error",
+                       Text: "Idle error",
+               },
+       )
+       require.Equal(t, "[connected to Example]\n> "+
+               "\r\x1b[2KIdle notice\n> "+
+               "\r\x1b[2K\x1b[1mIdle report\x1b[0m\n> "+
+               "\r\x1b[2K[working]\nBusy notice\n\x1b[1mAnswer\x1b[0m\n[done]\n> "+
+               "\r\x1b[2K[error] Idle error\n> ", out.String())
+}
+
+func TestTerminalNonterminalEventsHaveNoPromptOrClearCommands(t *testing.T) {
+       var out strings.Builder
+
+       terminalTestShowEvents(t, &out,
+               Event{
+                       Kind: "ready",
+                       Text: "Example",
+               },
+               Event{
+                       Kind: "notice",
+                       Text: "Idle notice",
+               },
+               Event{Kind: "busy"},
+               Event{
+                       Kind: "notice",
+                       Text: "Busy notice",
+               },
+               Event{
+                       Kind: "reply",
+                       Text: "**Answer**",
+               },
+               Event{Kind: "idle"},
+               Event{
+                       Kind: "notice",
+                       Text: "Idle again",
+               },
+       )
+       require.Equal(t, "[connected to Example]\nIdle notice\n[working]\nBusy notice\n**Answer**\n[done]\nIdle again\n", out.String())
+}
+
+// TerminalTestOutput captures text while it supplies a test terminal
+// descriptor.
+type TerminalTestOutput struct {
+       strings.Builder
+       fd int
+}
+
+func (o *TerminalTestOutput) Fd() uintptr {
+       return uintptr(o.fd)
+}
+
+func terminalTestShowEvents(t *testing.T, out io.Writer, events ...Event) {
+       t.Helper()
+
+       server, client := net.Pipe()
+       done := make(chan error, 1)
+       go func() {
+               done <- terminalShowEvents(client, out)
+       }()
+
+       encoder := json.NewEncoder(server)
+       for _, event := range events {
+               require.NoError(t, encoder.Encode(event))
+       }
+
+       require.NoError(t, server.Close())
+       require.NoError(t, <-done)
+       require.NoError(t, client.Close())
+}