mirror of
https://github.com/chenasraf/watchr.git
synced 2026-05-17 17:28:06 +00:00
test: add tests
This commit is contained in:
@@ -133,7 +133,9 @@ func TestBindFlags(t *testing.T) {
|
||||
flags.Int("line-width", 6, "")
|
||||
flags.String("prompt", "watchr> ", "")
|
||||
flags.String("refresh", "0", "")
|
||||
flags.Bool("refresh-from-start", false, "")
|
||||
flags.Bool("no-line-numbers", false, "")
|
||||
flags.Bool("interactive", false, "")
|
||||
|
||||
// Parse with custom values
|
||||
err := flags.Parse([]string{"--shell=bash", "--preview-size=50%", "--line-width=8"})
|
||||
@@ -239,7 +241,9 @@ preview-size: "60%"
|
||||
flags.Int("line-width", 6, "")
|
||||
flags.String("prompt", "watchr> ", "")
|
||||
flags.String("refresh", "0", "")
|
||||
flags.Bool("refresh-from-start", false, "")
|
||||
flags.Bool("no-line-numbers", false, "")
|
||||
flags.Bool("interactive", false, "")
|
||||
|
||||
// Override shell via flag
|
||||
err := flags.Parse([]string{"--shell=bash"})
|
||||
@@ -556,6 +560,107 @@ func TestGetDuration(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshFromStartDefault(t *testing.T) {
|
||||
_, cleanup := isolateConfig(t)
|
||||
defer cleanup()
|
||||
|
||||
Init()
|
||||
|
||||
// Default should be false
|
||||
if got := GetBool(KeyRefreshFromStart); got != false {
|
||||
t.Errorf("expected default refresh-from-start false, got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshFromStartFromConfigFile(t *testing.T) {
|
||||
tmpDir, cleanup := isolateConfig(t)
|
||||
defer cleanup()
|
||||
|
||||
// Create config file with refresh-from-start: true
|
||||
configPath := filepath.Join(tmpDir, "watchr.yaml")
|
||||
configContent := `refresh-from-start: true
|
||||
`
|
||||
if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil {
|
||||
t.Fatalf("failed to write config file: %v", err)
|
||||
}
|
||||
|
||||
Init()
|
||||
|
||||
if got := GetBool(KeyRefreshFromStart); got != true {
|
||||
t.Errorf("expected refresh-from-start true from config file, got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshFromStartFromFlag(t *testing.T) {
|
||||
_, cleanup := isolateConfig(t)
|
||||
defer cleanup()
|
||||
|
||||
Init()
|
||||
|
||||
// Create flags and parse
|
||||
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
flags.String("shell", "sh", "")
|
||||
flags.String("preview-size", "40%", "")
|
||||
flags.String("preview-position", "bottom", "")
|
||||
flags.Int("line-width", 6, "")
|
||||
flags.String("prompt", "watchr> ", "")
|
||||
flags.String("refresh", "0", "")
|
||||
flags.Bool("refresh-from-start", false, "")
|
||||
flags.Bool("no-line-numbers", false, "")
|
||||
flags.Bool("interactive", false, "")
|
||||
|
||||
// Parse with refresh-from-start=true
|
||||
err := flags.Parse([]string{"--refresh-from-start=true"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse flags: %v", err)
|
||||
}
|
||||
|
||||
BindFlags(flags)
|
||||
|
||||
if got := GetBool(KeyRefreshFromStart); got != true {
|
||||
t.Errorf("expected refresh-from-start true from flag, got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshFromStartFlagOverridesConfig(t *testing.T) {
|
||||
tmpDir, cleanup := isolateConfig(t)
|
||||
defer cleanup()
|
||||
|
||||
// Create config file with refresh-from-start: true
|
||||
configPath := filepath.Join(tmpDir, "watchr.yaml")
|
||||
configContent := `refresh-from-start: true
|
||||
`
|
||||
if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil {
|
||||
t.Fatalf("failed to write config file: %v", err)
|
||||
}
|
||||
|
||||
Init()
|
||||
|
||||
// Create flags and parse with refresh-from-start=false
|
||||
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
flags.String("shell", "sh", "")
|
||||
flags.String("preview-size", "40%", "")
|
||||
flags.String("preview-position", "bottom", "")
|
||||
flags.Int("line-width", 6, "")
|
||||
flags.String("prompt", "watchr> ", "")
|
||||
flags.String("refresh", "0", "")
|
||||
flags.Bool("refresh-from-start", false, "")
|
||||
flags.Bool("no-line-numbers", false, "")
|
||||
flags.Bool("interactive", false, "")
|
||||
|
||||
err := flags.Parse([]string{"--refresh-from-start=false"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse flags: %v", err)
|
||||
}
|
||||
|
||||
BindFlags(flags)
|
||||
|
||||
// Flag should override config
|
||||
if got := GetBool(KeyRefreshFromStart); got != false {
|
||||
t.Errorf("expected refresh-from-start false (flag override), got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshDurationFromConfigFile(t *testing.T) {
|
||||
tmpDir, cleanup := isolateConfig(t)
|
||||
defer cleanup()
|
||||
|
||||
@@ -378,3 +378,147 @@ func TestSanitizeLine(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunStreaming(t *testing.T) {
|
||||
r := NewRunner("sh", "echo 'line1'; echo 'line2'; echo 'line3'")
|
||||
ctx := context.Background()
|
||||
|
||||
result := r.RunStreaming(ctx, nil)
|
||||
|
||||
// Wait for completion
|
||||
for !result.IsDone() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
lines := result.GetLines()
|
||||
if len(lines) != 3 {
|
||||
t.Fatalf("expected 3 lines, got %d", len(lines))
|
||||
}
|
||||
|
||||
if lines[0].Content != "line1" {
|
||||
t.Errorf("expected first line 'line1', got %q", lines[0].Content)
|
||||
}
|
||||
|
||||
if result.GetCurrentLineCount() != 3 {
|
||||
t.Errorf("expected CurrentLineCount 3, got %d", result.GetCurrentLineCount())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunStreamingWithPreviousLines(t *testing.T) {
|
||||
// Previous lines that should be overwritten
|
||||
prevLines := []Line{
|
||||
{Number: 1, Content: "old1"},
|
||||
{Number: 2, Content: "old2"},
|
||||
{Number: 3, Content: "old3"},
|
||||
{Number: 4, Content: "old4"},
|
||||
{Number: 5, Content: "old5"},
|
||||
}
|
||||
|
||||
r := NewRunner("sh", "echo 'new1'; echo 'new2'; echo 'new3'")
|
||||
ctx := context.Background()
|
||||
|
||||
result := r.RunStreaming(ctx, prevLines)
|
||||
|
||||
// Verify PrevLineCount is set
|
||||
if result.PrevLineCount != 5 {
|
||||
t.Errorf("expected PrevLineCount 5, got %d", result.PrevLineCount)
|
||||
}
|
||||
|
||||
// Wait for completion
|
||||
for !result.IsDone() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
lines := result.GetLines()
|
||||
|
||||
// Should still have 5 lines (3 new + 2 old remaining)
|
||||
if len(lines) != 5 {
|
||||
t.Fatalf("expected 5 lines (in-place update), got %d", len(lines))
|
||||
}
|
||||
|
||||
// First 3 lines should be overwritten
|
||||
if lines[0].Content != "new1" {
|
||||
t.Errorf("expected line 0 'new1', got %q", lines[0].Content)
|
||||
}
|
||||
if lines[1].Content != "new2" {
|
||||
t.Errorf("expected line 1 'new2', got %q", lines[1].Content)
|
||||
}
|
||||
if lines[2].Content != "new3" {
|
||||
t.Errorf("expected line 2 'new3', got %q", lines[2].Content)
|
||||
}
|
||||
|
||||
// Remaining lines should be old (not touched)
|
||||
if lines[3].Content != "old4" {
|
||||
t.Errorf("expected line 3 'old4', got %q", lines[3].Content)
|
||||
}
|
||||
if lines[4].Content != "old5" {
|
||||
t.Errorf("expected line 4 'old5', got %q", lines[4].Content)
|
||||
}
|
||||
|
||||
// CurrentLineCount should be 3 (only new lines written)
|
||||
if result.GetCurrentLineCount() != 3 {
|
||||
t.Errorf("expected CurrentLineCount 3, got %d", result.GetCurrentLineCount())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunStreamingMoreLinesThanPrevious(t *testing.T) {
|
||||
// Previous lines (fewer than new output)
|
||||
prevLines := []Line{
|
||||
{Number: 1, Content: "old1"},
|
||||
{Number: 2, Content: "old2"},
|
||||
}
|
||||
|
||||
r := NewRunner("sh", "echo 'new1'; echo 'new2'; echo 'new3'; echo 'new4'")
|
||||
ctx := context.Background()
|
||||
|
||||
result := r.RunStreaming(ctx, prevLines)
|
||||
|
||||
// Wait for completion
|
||||
for !result.IsDone() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
lines := result.GetLines()
|
||||
|
||||
// Should have 4 lines (2 overwritten + 2 appended)
|
||||
if len(lines) != 4 {
|
||||
t.Fatalf("expected 4 lines, got %d", len(lines))
|
||||
}
|
||||
|
||||
// All lines should be new
|
||||
for i, expected := range []string{"new1", "new2", "new3", "new4"} {
|
||||
if lines[i].Content != expected {
|
||||
t.Errorf("expected line %d %q, got %q", i, expected, lines[i].Content)
|
||||
}
|
||||
}
|
||||
|
||||
if result.GetCurrentLineCount() != 4 {
|
||||
t.Errorf("expected CurrentLineCount 4, got %d", result.GetCurrentLineCount())
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamingResultThreadSafety(t *testing.T) {
|
||||
r := NewRunner("sh", "for i in $(seq 1 100); do echo line$i; done")
|
||||
ctx := context.Background()
|
||||
|
||||
result := r.RunStreaming(ctx, nil)
|
||||
|
||||
// Concurrently read while streaming
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
for !result.IsDone() {
|
||||
_ = result.GetLines()
|
||||
_ = result.LineCount()
|
||||
_ = result.GetCurrentLineCount()
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
done <- true
|
||||
}()
|
||||
|
||||
<-done
|
||||
|
||||
// Should complete without race conditions
|
||||
if result.LineCount() != 100 {
|
||||
t.Errorf("expected 100 lines, got %d", result.LineCount())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,3 +284,132 @@ func TestVisibleLines(t *testing.T) {
|
||||
t.Errorf("expected %d visible lines with absolute preview size, got %d", expected, visible)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateFilteredPreservesOffset(t *testing.T) {
|
||||
cfg := Config{
|
||||
Command: "echo test",
|
||||
Shell: "sh",
|
||||
}
|
||||
|
||||
m := initialModel(cfg)
|
||||
m.height = 20 // Enough for visibleLines to return > 0
|
||||
|
||||
// Add many test lines
|
||||
for i := 1; i <= 100; i++ {
|
||||
m.lines = append(m.lines, runner.Line{Number: i, Content: "line content"})
|
||||
}
|
||||
|
||||
// Set initial state with offset
|
||||
m.filter = ""
|
||||
m.updateFiltered()
|
||||
m.offset = 50
|
||||
m.cursor = 55
|
||||
|
||||
// Simulate streaming update - add more lines without changing filter
|
||||
m.lines = append(m.lines, runner.Line{Number: 101, Content: "new line"})
|
||||
m.updateFiltered()
|
||||
|
||||
// Offset should be preserved (or clamped if necessary)
|
||||
if m.offset < 50 {
|
||||
t.Errorf("expected offset to be preserved (>= 50), got %d", m.offset)
|
||||
}
|
||||
|
||||
// Cursor should be preserved
|
||||
if m.cursor != 55 {
|
||||
t.Errorf("expected cursor to be preserved at 55, got %d", m.cursor)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateFilteredClampsOffsetWhenNeeded(t *testing.T) {
|
||||
cfg := Config{
|
||||
Command: "echo test",
|
||||
Shell: "sh",
|
||||
}
|
||||
|
||||
m := initialModel(cfg)
|
||||
m.height = 20
|
||||
|
||||
// Add test lines
|
||||
for i := 1; i <= 100; i++ {
|
||||
m.lines = append(m.lines, runner.Line{Number: i, Content: "line content"})
|
||||
}
|
||||
|
||||
m.filter = ""
|
||||
m.updateFiltered()
|
||||
m.offset = 90
|
||||
m.cursor = 95
|
||||
|
||||
// Now filter to fewer lines
|
||||
m.filter = "xyz" // No matches
|
||||
m.updateFiltered()
|
||||
|
||||
// Offset should be clamped to valid range
|
||||
if m.offset != 0 {
|
||||
t.Errorf("expected offset to be clamped to 0, got %d", m.offset)
|
||||
}
|
||||
|
||||
// Cursor should be clamped
|
||||
if m.cursor != 0 {
|
||||
t.Errorf("expected cursor to be clamped to 0, got %d", m.cursor)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigRefreshFromStart(t *testing.T) {
|
||||
// Test with RefreshFromStart false (default)
|
||||
cfg := Config{
|
||||
Command: "echo test",
|
||||
Shell: "sh",
|
||||
RefreshInterval: 5 * time.Second,
|
||||
RefreshFromStart: false,
|
||||
}
|
||||
|
||||
if cfg.RefreshFromStart {
|
||||
t.Error("expected RefreshFromStart to be false by default")
|
||||
}
|
||||
|
||||
// Test with RefreshFromStart true
|
||||
cfg.RefreshFromStart = true
|
||||
if !cfg.RefreshFromStart {
|
||||
t.Error("expected RefreshFromStart to be true after setting")
|
||||
}
|
||||
}
|
||||
|
||||
func TestModelUserScrolled(t *testing.T) {
|
||||
cfg := Config{
|
||||
Command: "echo test",
|
||||
Shell: "sh",
|
||||
}
|
||||
|
||||
m := initialModel(cfg)
|
||||
|
||||
// Initially should be false
|
||||
if m.userScrolled {
|
||||
t.Error("expected userScrolled to be false initially")
|
||||
}
|
||||
|
||||
// After setting, should be true
|
||||
m.userScrolled = true
|
||||
if !m.userScrolled {
|
||||
t.Error("expected userScrolled to be true after setting")
|
||||
}
|
||||
}
|
||||
|
||||
func TestModelRefreshGeneration(t *testing.T) {
|
||||
cfg := Config{
|
||||
Command: "echo test",
|
||||
Shell: "sh",
|
||||
}
|
||||
|
||||
m := initialModel(cfg)
|
||||
|
||||
// Initially should be 0
|
||||
if m.refreshGeneration != 0 {
|
||||
t.Errorf("expected refreshGeneration to be 0 initially, got %d", m.refreshGeneration)
|
||||
}
|
||||
|
||||
// After incrementing
|
||||
m.refreshGeneration++
|
||||
if m.refreshGeneration != 1 {
|
||||
t.Errorf("expected refreshGeneration to be 1 after increment, got %d", m.refreshGeneration)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user