mirror of
https://github.com/chenasraf/watchr.git
synced 2026-05-17 17:28:06 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4862882a9 | ||
| 6a2df0a15d |
@@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [1.10.1](https://github.com/chenasraf/watchr/compare/v1.10.0...v1.10.1) (2026-05-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* command re-running caused early line truncating ([6a2df0a](https://github.com/chenasraf/watchr/commit/6a2df0a15d4df8f3e002faef5f2f822c2e0dc772))
|
||||||
|
|
||||||
## [1.10.0](https://github.com/chenasraf/watchr/compare/v1.9.0...v1.10.0) (2026-03-24)
|
## [1.10.0](https://github.com/chenasraf/watchr/compare/v1.9.0...v1.10.0) (2026-03-24)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
114
internal/ui/reload_test.go
Normal file
114
internal/ui/reload_test.go
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/chenasraf/watchr/internal/runner"
|
||||||
|
)
|
||||||
|
|
||||||
|
func waitDone(t *testing.T, m *model) {
|
||||||
|
t.Helper()
|
||||||
|
deadline := time.Now().Add(5 * time.Second)
|
||||||
|
for time.Now().Before(deadline) {
|
||||||
|
if m.streamResult != nil && m.streamResult.IsDone() {
|
||||||
|
// fire one tick after done so model state syncs
|
||||||
|
_, _ = m.Update(streamTickMsg(time.Now()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, _ = m.Update(streamTickMsg(time.Now()))
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
t.Fatal("stream did not complete")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestReload_SameLineCountUpdatesInPlace verifies that when the new run
|
||||||
|
// produces the same number of lines as the previous run, the new content
|
||||||
|
// (delivered via in-place updates) is reflected in m.lines.
|
||||||
|
func TestReload_SameLineCountUpdatesInPlace(t *testing.T) {
|
||||||
|
cfg := Config{Command: "echo new1; echo new2; echo new3", Shell: "sh"}
|
||||||
|
m := testModel(cfg)
|
||||||
|
m.height = 30
|
||||||
|
m.width = 80
|
||||||
|
|
||||||
|
// Pretend a previous run left 3 lines of stale content
|
||||||
|
m.lines = []runner.Line{
|
||||||
|
{Number: 1, Content: "old1"},
|
||||||
|
{Number: 2, Content: "old2"},
|
||||||
|
{Number: 3, Content: "old3"},
|
||||||
|
}
|
||||||
|
m.lastLineCount = 3
|
||||||
|
m.updateFiltered()
|
||||||
|
|
||||||
|
_, _ = m.actionReload()
|
||||||
|
waitDone(t, m)
|
||||||
|
|
||||||
|
if len(m.lines) != 3 {
|
||||||
|
t.Fatalf("expected 3 lines, got %d", len(m.lines))
|
||||||
|
}
|
||||||
|
for i, want := range []string{"new1", "new2", "new3"} {
|
||||||
|
if m.lines[i].Content != want {
|
||||||
|
t.Errorf("line %d: expected %q, got %q", i, want, m.lines[i].Content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestReload_FewerLinesTrimsToNewRun verifies that when the new run produces
|
||||||
|
// fewer lines than the previous run, m.lines is trimmed AND shows the new
|
||||||
|
// content in the surviving slots (not stale prev content).
|
||||||
|
func TestReload_FewerLinesTrimsToNewRun(t *testing.T) {
|
||||||
|
cfg := Config{Command: "echo new1; echo new2", Shell: "sh"}
|
||||||
|
m := testModel(cfg)
|
||||||
|
m.height = 30
|
||||||
|
m.width = 80
|
||||||
|
|
||||||
|
m.lines = []runner.Line{
|
||||||
|
{Number: 1, Content: "old1"},
|
||||||
|
{Number: 2, Content: "old2"},
|
||||||
|
{Number: 3, Content: "old3"},
|
||||||
|
{Number: 4, Content: "old4"},
|
||||||
|
}
|
||||||
|
m.lastLineCount = 4
|
||||||
|
m.updateFiltered()
|
||||||
|
|
||||||
|
_, _ = m.actionReload()
|
||||||
|
waitDone(t, m)
|
||||||
|
|
||||||
|
if len(m.lines) != 2 {
|
||||||
|
t.Fatalf("expected 2 lines after reload, got %d", len(m.lines))
|
||||||
|
}
|
||||||
|
for i, want := range []string{"new1", "new2"} {
|
||||||
|
if m.lines[i].Content != want {
|
||||||
|
t.Errorf("line %d: expected %q, got %q", i, want, m.lines[i].Content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestReload_MoreLines verifies that when the new run produces more lines
|
||||||
|
// than the previous run, all new lines are visible.
|
||||||
|
func TestReload_MoreLines(t *testing.T) {
|
||||||
|
cfg := Config{Command: "echo a; echo b; echo c; echo d; echo e", Shell: "sh"}
|
||||||
|
m := testModel(cfg)
|
||||||
|
m.height = 30
|
||||||
|
m.width = 80
|
||||||
|
|
||||||
|
m.lines = []runner.Line{
|
||||||
|
{Number: 1, Content: "old1"},
|
||||||
|
{Number: 2, Content: "old2"},
|
||||||
|
{Number: 3, Content: "old3"},
|
||||||
|
}
|
||||||
|
m.lastLineCount = 3
|
||||||
|
m.updateFiltered()
|
||||||
|
|
||||||
|
_, _ = m.actionReload()
|
||||||
|
waitDone(t, m)
|
||||||
|
|
||||||
|
if len(m.lines) != 5 {
|
||||||
|
t.Fatalf("expected 5 lines, got %d", len(m.lines))
|
||||||
|
}
|
||||||
|
for i, want := range []string{"a", "b", "c", "d", "e"} {
|
||||||
|
if m.lines[i].Content != want {
|
||||||
|
t.Errorf("line %d: expected %q, got %q", i, want, m.lines[i].Content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -70,7 +70,9 @@ func (m *model) startStreaming() tea.Cmd {
|
|||||||
m.streamResult = m.runner.RunStreaming(m.ctx, m.lines)
|
m.streamResult = m.runner.RunStreaming(m.ctx, m.lines)
|
||||||
m.streaming = true
|
m.streaming = true
|
||||||
m.loading = true
|
m.loading = true
|
||||||
m.lastLineCount = len(m.lines)
|
// lastLineCount tracks the streamResult's CurrentLineCount (lines produced
|
||||||
|
// by the current run), which starts at 0 for a fresh streaming result.
|
||||||
|
m.lastLineCount = 0
|
||||||
m.exitCode = -1
|
m.exitCode = -1
|
||||||
m.errorMsg = ""
|
m.errorMsg = ""
|
||||||
m.userScrolled = false
|
m.userScrolled = false
|
||||||
@@ -116,16 +118,23 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for new lines
|
isDone := m.streamResult.IsDone()
|
||||||
newLines := m.streamResult.GetLines()
|
currentCount := m.streamResult.GetCurrentLineCount()
|
||||||
newCount := len(newLines)
|
|
||||||
|
|
||||||
if newCount != m.lastLineCount {
|
// Sync m.lines whenever the stream has produced new line writes since
|
||||||
|
// the last tick (in-place edits and appends both bump CurrentLineCount),
|
||||||
|
// or once on completion so the trim-to-currentCount path runs.
|
||||||
|
if currentCount != m.lastLineCount || isDone {
|
||||||
|
newLines := m.streamResult.GetLines()
|
||||||
|
// On completion, drop any leftover slots that the new run never
|
||||||
|
// wrote into — they still hold previous-run content.
|
||||||
|
if isDone && currentCount < len(newLines) {
|
||||||
|
newLines = newLines[:currentCount]
|
||||||
|
}
|
||||||
m.lines = newLines
|
m.lines = newLines
|
||||||
m.lastLineCount = newCount
|
m.lastLineCount = currentCount
|
||||||
m.updateFiltered()
|
m.updateFiltered()
|
||||||
|
|
||||||
// Auto-scroll to bottom if user hasn't manually scrolled
|
|
||||||
if !m.userScrolled {
|
if !m.userScrolled {
|
||||||
visible := m.visibleLines()
|
visible := m.visibleLines()
|
||||||
if visible > 0 {
|
if visible > 0 {
|
||||||
@@ -135,8 +144,7 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if command completed
|
if isDone {
|
||||||
if m.streamResult.IsDone() {
|
|
||||||
m.streaming = false
|
m.streaming = false
|
||||||
m.loading = false
|
m.loading = false
|
||||||
m.exitCode = m.streamResult.ExitCode
|
m.exitCode = m.streamResult.ExitCode
|
||||||
@@ -144,13 +152,6 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.errorMsg = m.streamResult.Error.Error()
|
m.errorMsg = m.streamResult.Error.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trim excess lines from previous run
|
|
||||||
currentCount := m.streamResult.GetCurrentLineCount()
|
|
||||||
if currentCount < len(m.lines) {
|
|
||||||
m.lines = m.lines[:currentCount]
|
|
||||||
m.updateFiltered()
|
|
||||||
}
|
|
||||||
|
|
||||||
// If auto-refresh is enabled and timer starts from end, schedule the next run
|
// If auto-refresh is enabled and timer starts from end, schedule the next run
|
||||||
if m.config.RefreshInterval > 0 && !m.config.RefreshFromStart {
|
if m.config.RefreshInterval > 0 && !m.config.RefreshFromStart {
|
||||||
m.refreshStartTime = time.Now()
|
m.refreshStartTime = time.Now()
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.10.0
|
1.10.1
|
||||||
|
|||||||
Reference in New Issue
Block a user