Files
picoclaw/pkg/channels/tool_feedback_animator_test.go
T
lxowalle 451db2f5d8 Feat(channels): unify animated tool feedback across chat channels and Pico (#2622)
* feat(channels): unify tool feedback animation across discord telegram and feishu

* fix(tool-feedback): unify fallback and single-message delivery

* fix(channels): finalize tool feedback in place

* fix ci

* feat: improve tool feedback

* fix review blockers in pico token cache and tool feedback

fix(provider): preserve function thought signatures

fix(feishu): recover tool feedback after edit fallback

* * delete dead code

* fix(pico): clean up tool feedback progress state

* fix ci

* fix(web): preserve tool feedback line breaks in chat

* fix(channels): preserve tool feedback progress state

fix(pico): preserve context usage when finalizing tool feedback

chore: record branch review pass

fix: preserve tool feedback finalization state

fix(web): handle pico history update fallback

* fix ci
2026-04-23 10:35:50 +08:00

122 lines
3.8 KiB
Go

package channels
import (
"context"
"errors"
"testing"
)
func TestFormatAnimatedToolFeedbackContent(t *testing.T) {
got := formatAnimatedToolFeedbackContent("🔧 `read_file`\nReading config file", "running..")
want := "🔧 `read_filerunning..`\nReading config file"
if got != want {
t.Fatalf("formatAnimatedToolFeedbackContent() = %q, want %q", got, want)
}
}
func TestInitialAnimatedToolFeedbackContent(t *testing.T) {
got := InitialAnimatedToolFeedbackContent("🔧 `exec`\nRunning command")
want := "🔧 `exec`\nRunning command"
if got != want {
t.Fatalf("InitialAnimatedToolFeedbackContent() = %q, want %q", got, want)
}
}
func TestFormatAnimatedToolFeedbackContent_WithoutCodeSpan(t *testing.T) {
got := formatAnimatedToolFeedbackContent("hello", "running..")
want := "hellorunning.."
if got != want {
t.Fatalf("formatAnimatedToolFeedbackContent() without code span = %q, want %q", got, want)
}
}
func TestToolFeedbackAnimator_RecordCurrentAndClear(t *testing.T) {
animator := NewToolFeedbackAnimator(nil)
animator.Record("chat-1", "msg-1", "🔧 `read_file`")
msgID, ok := animator.Current("chat-1")
if !ok || msgID != "msg-1" {
t.Fatalf("Current() = (%q, %v), want (msg-1, true)", msgID, ok)
}
animator.Clear("chat-1")
msgID, ok = animator.Current("chat-1")
if ok || msgID != "" {
t.Fatalf("Current() after Clear = (%q, %v), want (\"\", false)", msgID, ok)
}
}
func TestToolFeedbackAnimator_TakeStopsTrackingAndReturnsState(t *testing.T) {
animator := NewToolFeedbackAnimator(nil)
animator.Record("chat-1", "msg-1", "🔧 `read_file`\nChecking config")
msgID, baseContent, ok := animator.Take("chat-1")
if !ok {
t.Fatal("Take() = not found, want tracked message")
}
if msgID != "msg-1" {
t.Fatalf("Take() msgID = %q, want msg-1", msgID)
}
if baseContent != "🔧 `read_file`\nChecking config" {
t.Fatalf("Take() baseContent = %q", baseContent)
}
if _, ok := animator.Current("chat-1"); ok {
t.Fatal("expected tracked message to be removed after Take()")
}
}
func TestToolFeedbackAnimator_UpdateStopsTrackingBeforeEdit(t *testing.T) {
var animator *ToolFeedbackAnimator
animator = NewToolFeedbackAnimator(func(_ context.Context, chatID, messageID, content string) error {
if _, ok := animator.Current(chatID); ok {
t.Fatal("expected tracked tool feedback to be stopped before edit")
}
if messageID != "msg-1" {
t.Fatalf("messageID = %q, want msg-1", messageID)
}
if content != "🔧 `write_file`\nUpdating config" {
t.Fatalf("content = %q, want updated animated content", content)
}
return nil
})
defer animator.StopAll()
animator.Record("chat-1", "msg-1", "🔧 `read_file`\nChecking config")
msgID, handled, err := animator.Update(context.Background(), "chat-1", "🔧 `write_file`\nUpdating config")
if err != nil {
t.Fatalf("Update() error = %v", err)
}
if !handled {
t.Fatal("Update() handled = false, want true")
}
if msgID != "msg-1" {
t.Fatalf("Update() msgID = %q, want msg-1", msgID)
}
}
func TestToolFeedbackAnimator_UpdateFailureRestoresTracking(t *testing.T) {
editErr := errors.New("edit failed")
animator := NewToolFeedbackAnimator(func(context.Context, string, string, string) error {
return editErr
})
defer animator.StopAll()
animator.Record("chat-1", "msg-1", "🔧 `read_file`\nChecking config")
msgID, handled, err := animator.Update(context.Background(), "chat-1", "🔧 `write_file`\nUpdating config")
if !handled {
t.Fatal("Update() handled = false, want true")
}
if !errors.Is(err, editErr) {
t.Fatalf("Update() error = %v, want editErr", err)
}
if msgID != "" {
t.Fatalf("Update() msgID = %q, want empty on failed edit", msgID)
}
if currentID, ok := animator.Current("chat-1"); !ok || currentID != "msg-1" {
t.Fatalf("Current() after failed Update = (%q, %v), want (msg-1, true)", currentID, ok)
}
}