Mirrors the existing scratchpad_* tools end-to-end: catalog schema, dispatch, ToolHost.ScratchpadDelete, and a host method that delegates to scratchpad.Store.Delete and fires scratchpadsChanged() on success so the sidebar refreshes. Missing-pad errors surface rather than being masked. Resolves the [MCP SCRATCHPAD DELETE] TODO item.
138 lines
3.5 KiB
Go
138 lines
3.5 KiB
Go
package app
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/hjbdev/patterm/internal/preset"
|
|
"github.com/hjbdev/patterm/internal/scratchpad"
|
|
)
|
|
|
|
func silenceStdout(t *testing.T) {
|
|
t.Helper()
|
|
old := os.Stdout
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatalf("pipe stdout: %v", err)
|
|
}
|
|
done := make(chan struct{})
|
|
go func() {
|
|
_, _ = io.Copy(io.Discard, r)
|
|
close(done)
|
|
}()
|
|
os.Stdout = w
|
|
t.Cleanup(func() {
|
|
os.Stdout = old
|
|
_ = w.Close()
|
|
<-done
|
|
_ = r.Close()
|
|
})
|
|
}
|
|
|
|
func newScratchpadDeleteTestState(t *testing.T) (*uiState, *scratchpad.Store) {
|
|
t.Helper()
|
|
t.Setenv("XDG_DATA_HOME", t.TempDir())
|
|
pads, err := scratchpad.Open("scratchpad-delete-test")
|
|
if err != nil {
|
|
t.Fatalf("scratchpad.Open: %v", err)
|
|
}
|
|
sess := NewSession(t.TempDir(), "scratchpad-delete-test")
|
|
t.Cleanup(sess.Shutdown)
|
|
st := &uiState{
|
|
sess: sess,
|
|
pads: pads,
|
|
hostCols: 120,
|
|
hostRows: 40,
|
|
chromeWake: make(chan struct{}, 1),
|
|
}
|
|
return st, pads
|
|
}
|
|
|
|
func TestDeletingFocusedScratchpadFocusesAnotherPad(t *testing.T) {
|
|
silenceStdout(t)
|
|
st, pads := newScratchpadDeleteTestState(t)
|
|
if _, err := pads.Write("alpha.md", "alpha", ""); err != nil {
|
|
t.Fatalf("write alpha: %v", err)
|
|
}
|
|
if _, err := pads.Write("beta.md", "beta", ""); err != nil {
|
|
t.Fatalf("write beta: %v", err)
|
|
}
|
|
st.focusedPad = "alpha.md"
|
|
st.focusedName = "alpha.md"
|
|
st.padOffsetName = "alpha.md"
|
|
st.padOffset = 3
|
|
|
|
st.handlePadDelete("alpha.md")
|
|
|
|
if st.focusedPad != "beta.md" {
|
|
t.Fatalf("focusedPad = %q, want beta.md", st.focusedPad)
|
|
}
|
|
if st.focusedID != "" {
|
|
t.Fatalf("focusedID = %q, want empty while another pad is focused", st.focusedID)
|
|
}
|
|
if st.padOffset != 0 || st.padOffsetName != "beta.md" {
|
|
t.Fatalf("pad offset = (%q,%d), want (beta.md,0)", st.padOffsetName, st.padOffset)
|
|
}
|
|
}
|
|
|
|
func TestDeletingLastFocusedScratchpadFocusesRunningChild(t *testing.T) {
|
|
silenceStdout(t)
|
|
st, pads := newScratchpadDeleteTestState(t)
|
|
if _, err := pads.Write("only.md", "only", ""); err != nil {
|
|
t.Fatalf("write only: %v", err)
|
|
}
|
|
child := makeFakeChild("pid", "devserver", KindCommand)
|
|
addChild(st.sess, child)
|
|
st.focusedPad = "only.md"
|
|
st.focusedName = "only.md"
|
|
|
|
st.handlePadDelete("only.md")
|
|
|
|
if st.focusedPad != "" {
|
|
t.Fatalf("focusedPad = %q, want empty after falling back to child", st.focusedPad)
|
|
}
|
|
if st.focusedID != "pid" {
|
|
t.Fatalf("focusedID = %q, want pid", st.focusedID)
|
|
}
|
|
}
|
|
|
|
type scratchpadChangeRecorder struct {
|
|
count int
|
|
}
|
|
|
|
func (r *scratchpadChangeRecorder) scratchpadsChanged() {
|
|
r.count++
|
|
}
|
|
|
|
func TestToolHostScratchpadDeleteRemovesPadAndRefreshes(t *testing.T) {
|
|
t.Setenv("XDG_DATA_HOME", t.TempDir())
|
|
pads, err := scratchpad.Open("scratchpad-delete-host-test")
|
|
if err != nil {
|
|
t.Fatalf("scratchpad.Open: %v", err)
|
|
}
|
|
if _, err := pads.Write("doomed.md", "content", ""); err != nil {
|
|
t.Fatalf("write doomed.md: %v", err)
|
|
}
|
|
recorder := &scratchpadChangeRecorder{}
|
|
host := newToolHost(nil, pads, nil, preset.Set{}, nil, 120, 40)
|
|
host.scratch = recorder
|
|
|
|
if err := host.ScratchpadDelete("doomed.md"); err != nil {
|
|
t.Fatalf("ScratchpadDelete: %v", err)
|
|
}
|
|
if recorder.count != 1 {
|
|
t.Fatalf("scratchpadsChanged calls = %d, want 1", recorder.count)
|
|
}
|
|
if _, _, err := pads.Read("doomed.md"); !errors.Is(err, os.ErrNotExist) {
|
|
t.Fatalf("read deleted pad error = %v, want os.ErrNotExist", err)
|
|
}
|
|
if err := host.ScratchpadDelete("doomed.md"); !errors.Is(err, os.ErrNotExist) {
|
|
t.Fatalf("delete missing error = %v, want os.ErrNotExist", err)
|
|
}
|
|
if recorder.count != 1 {
|
|
t.Fatalf("scratchpadsChanged calls after failed delete = %d, want 1", recorder.count)
|
|
}
|
|
}
|