mirror of
https://github.com/tw93/Mole.git
synced 2026-03-22 16:45:07 +00:00
Fix cleanup regressions and analyze navigation
Refs #605 #607 #608 #609 #610
This commit is contained in:
@@ -11,6 +11,8 @@ import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
func resetOverviewSnapshotForTest() {
|
||||
@@ -182,6 +184,68 @@ func TestOverviewStoreAndLoad(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateKeyEscGoesBackFromDirectoryView(t *testing.T) {
|
||||
m := model{
|
||||
path: "/tmp/child",
|
||||
history: []historyEntry{
|
||||
{
|
||||
Path: "/tmp",
|
||||
Entries: []dirEntry{{Name: "child", Path: "/tmp/child", Size: 1, IsDir: true}},
|
||||
TotalSize: 1,
|
||||
Selected: 0,
|
||||
EntryOffset: 0,
|
||||
},
|
||||
},
|
||||
entries: []dirEntry{{Name: "file.txt", Path: "/tmp/child/file.txt", Size: 1}},
|
||||
}
|
||||
|
||||
updated, cmd := m.updateKey(tea.KeyMsg{Type: tea.KeyEsc})
|
||||
if cmd != nil {
|
||||
t.Fatalf("expected no command when returning from cached history, got %v", cmd)
|
||||
}
|
||||
|
||||
got, ok := updated.(model)
|
||||
if !ok {
|
||||
t.Fatalf("expected model, got %T", updated)
|
||||
}
|
||||
if got.path != "/tmp" {
|
||||
t.Fatalf("expected path /tmp after Esc, got %s", got.path)
|
||||
}
|
||||
if got.status == "" {
|
||||
t.Fatalf("expected status to be updated after Esc navigation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateKeyCtrlCQuits(t *testing.T) {
|
||||
m := model{}
|
||||
|
||||
_, cmd := m.updateKey(tea.KeyMsg{Type: tea.KeyCtrlC})
|
||||
if cmd == nil {
|
||||
t.Fatalf("expected quit command for Ctrl+C")
|
||||
}
|
||||
if _, ok := cmd().(tea.QuitMsg); !ok {
|
||||
t.Fatalf("expected tea.QuitMsg from quit command")
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewShowsEscBackAndCtrlCQuitHints(t *testing.T) {
|
||||
m := model{
|
||||
path: "/tmp/project",
|
||||
history: []historyEntry{{Path: "/tmp"}},
|
||||
entries: []dirEntry{{Name: "cache", Path: "/tmp/project/cache", Size: 1, IsDir: true}},
|
||||
largeFiles: []fileEntry{{Name: "large.bin", Path: "/tmp/project/large.bin", Size: 1024}},
|
||||
totalSize: 1024,
|
||||
}
|
||||
|
||||
view := m.View()
|
||||
if !strings.Contains(view, "Esc Back") {
|
||||
t.Fatalf("expected Esc Back hint in view, got:\n%s", view)
|
||||
}
|
||||
if !strings.Contains(view, "Ctrl+C Quit") {
|
||||
t.Fatalf("expected Ctrl+C Quit hint in view, got:\n%s", view)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheSaveLoadRoundTrip(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
t.Setenv("HOME", home)
|
||||
|
||||
@@ -612,20 +612,22 @@ func (m model) updateKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
m.deleteConfirm = false
|
||||
m.deleteTarget = nil
|
||||
return m, nil
|
||||
case "ctrl+c":
|
||||
return m, tea.Quit
|
||||
default:
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
|
||||
switch msg.String() {
|
||||
case "q", "ctrl+c", "Q":
|
||||
case "q", "Q", "ctrl+c":
|
||||
return m, tea.Quit
|
||||
case "esc":
|
||||
if m.showLargeFiles {
|
||||
m.showLargeFiles = false
|
||||
return m, nil
|
||||
}
|
||||
return m, tea.Quit
|
||||
return m.goBack()
|
||||
case "up", "k", "K":
|
||||
if m.showLargeFiles {
|
||||
if m.largeSelected > 0 {
|
||||
@@ -666,53 +668,7 @@ func (m model) updateKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
m.showLargeFiles = false
|
||||
return m, nil
|
||||
}
|
||||
if len(m.history) == 0 {
|
||||
if !m.inOverviewMode() {
|
||||
return m, m.switchToOverviewMode()
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
last := m.history[len(m.history)-1]
|
||||
m.history = m.history[:len(m.history)-1]
|
||||
m.path = last.Path
|
||||
m.selected = last.Selected
|
||||
m.offset = last.EntryOffset
|
||||
m.largeSelected = last.LargeSelected
|
||||
m.largeOffset = last.LargeOffset
|
||||
m.isOverview = last.IsOverview
|
||||
if last.Dirty {
|
||||
// On overview return, refresh cached entries.
|
||||
if last.IsOverview {
|
||||
m.hydrateOverviewEntries()
|
||||
m.totalSize = sumKnownEntrySizes(m.entries)
|
||||
m.status = "Ready"
|
||||
m.scanning = false
|
||||
if nextPendingOverviewIndex(m.entries) >= 0 {
|
||||
m.overviewScanning = true
|
||||
return m, m.scheduleOverviewScans()
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
m.status = "Scanning..."
|
||||
m.scanning = true
|
||||
return m, tea.Batch(m.scanCmd(m.path), tickCmd())
|
||||
}
|
||||
m.entries = last.Entries
|
||||
m.largeFiles = last.LargeFiles
|
||||
m.totalSize = last.TotalSize
|
||||
m.clampEntrySelection()
|
||||
m.clampLargeSelection()
|
||||
if len(m.entries) == 0 {
|
||||
m.selected = 0
|
||||
} else if m.selected >= len(m.entries) {
|
||||
m.selected = len(m.entries) - 1
|
||||
}
|
||||
if m.selected < 0 {
|
||||
m.selected = 0
|
||||
}
|
||||
m.status = fmt.Sprintf("Scanned %s", humanizeBytes(m.totalSize))
|
||||
m.scanning = false
|
||||
return m, nil
|
||||
return m.goBack()
|
||||
case "r", "R":
|
||||
m.multiSelected = make(map[string]bool)
|
||||
m.largeMultiSelected = make(map[string]bool)
|
||||
@@ -962,6 +918,57 @@ func (m model) updateKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m model) goBack() (tea.Model, tea.Cmd) {
|
||||
if len(m.history) == 0 {
|
||||
if !m.inOverviewMode() {
|
||||
return m, m.switchToOverviewMode()
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
last := m.history[len(m.history)-1]
|
||||
m.history = m.history[:len(m.history)-1]
|
||||
m.path = last.Path
|
||||
m.selected = last.Selected
|
||||
m.offset = last.EntryOffset
|
||||
m.largeSelected = last.LargeSelected
|
||||
m.largeOffset = last.LargeOffset
|
||||
m.isOverview = last.IsOverview
|
||||
if last.Dirty {
|
||||
// On overview return, refresh cached entries.
|
||||
if last.IsOverview {
|
||||
m.hydrateOverviewEntries()
|
||||
m.totalSize = sumKnownEntrySizes(m.entries)
|
||||
m.status = "Ready"
|
||||
m.scanning = false
|
||||
if nextPendingOverviewIndex(m.entries) >= 0 {
|
||||
m.overviewScanning = true
|
||||
return m, m.scheduleOverviewScans()
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
m.status = "Scanning..."
|
||||
m.scanning = true
|
||||
return m, tea.Batch(m.scanCmd(m.path), tickCmd())
|
||||
}
|
||||
m.entries = last.Entries
|
||||
m.largeFiles = last.LargeFiles
|
||||
m.totalSize = last.TotalSize
|
||||
m.clampEntrySelection()
|
||||
m.clampLargeSelection()
|
||||
if len(m.entries) == 0 {
|
||||
m.selected = 0
|
||||
} else if m.selected >= len(m.entries) {
|
||||
m.selected = len(m.entries) - 1
|
||||
}
|
||||
if m.selected < 0 {
|
||||
m.selected = 0
|
||||
}
|
||||
m.status = fmt.Sprintf("Scanned %s", humanizeBytes(m.totalSize))
|
||||
m.scanning = false
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *model) switchToOverviewMode() tea.Cmd {
|
||||
m.isOverview = true
|
||||
m.path = "/"
|
||||
|
||||
@@ -327,31 +327,31 @@ func (m model) View() string {
|
||||
fmt.Fprintln(&b)
|
||||
if m.inOverviewMode() {
|
||||
if len(m.history) > 0 {
|
||||
fmt.Fprintf(&b, "%s↑↓←→ | Enter | R Refresh | O Open | F File | ← Back | Q Quit%s\n", colorGray, colorReset)
|
||||
fmt.Fprintf(&b, "%s↑↓←→ | Enter | R Refresh | O Open | F File | Esc Back | Q/Ctrl+C Quit%s\n", colorGray, colorReset)
|
||||
} else {
|
||||
fmt.Fprintf(&b, "%s↑↓→ | Enter | R Refresh | O Open | F File | Q Quit%s\n", colorGray, colorReset)
|
||||
fmt.Fprintf(&b, "%s↑↓→ | Enter | R Refresh | O Open | F File | Ctrl+C Quit%s\n", colorGray, colorReset)
|
||||
}
|
||||
} else if m.showLargeFiles {
|
||||
selectCount := len(m.largeMultiSelected)
|
||||
if selectCount > 0 {
|
||||
fmt.Fprintf(&b, "%s↑↓← | Space Select | R Refresh | O Open | F File | ⌫ Del %d | ← Back | Q Quit%s\n", colorGray, selectCount, colorReset)
|
||||
fmt.Fprintf(&b, "%s↑↓← | Space Select | R Refresh | O Open | F File | ⌫ Del %d | Esc Back | Q/Ctrl+C Quit%s\n", colorGray, selectCount, colorReset)
|
||||
} else {
|
||||
fmt.Fprintf(&b, "%s↑↓← | Space Select | R Refresh | O Open | F File | ⌫ Del | ← Back | Q Quit%s\n", colorGray, colorReset)
|
||||
fmt.Fprintf(&b, "%s↑↓← | Space Select | R Refresh | O Open | F File | ⌫ Del | Esc Back | Q/Ctrl+C Quit%s\n", colorGray, colorReset)
|
||||
}
|
||||
} else {
|
||||
largeFileCount := len(m.largeFiles)
|
||||
selectCount := len(m.multiSelected)
|
||||
if selectCount > 0 {
|
||||
if largeFileCount > 0 {
|
||||
fmt.Fprintf(&b, "%s↑↓←→ | Space Select | Enter | R Refresh | O Open | F File | ⌫ Del %d | T Top %d | Q Quit%s\n", colorGray, selectCount, largeFileCount, colorReset)
|
||||
fmt.Fprintf(&b, "%s↑↓←→ | Space Select | Enter | R Refresh | O Open | F File | ⌫ Del %d | T Top %d | Esc Back | Q/Ctrl+C Quit%s\n", colorGray, selectCount, largeFileCount, colorReset)
|
||||
} else {
|
||||
fmt.Fprintf(&b, "%s↑↓←→ | Space Select | Enter | R Refresh | O Open | F File | ⌫ Del %d | Q Quit%s\n", colorGray, selectCount, colorReset)
|
||||
fmt.Fprintf(&b, "%s↑↓←→ | Space Select | Enter | R Refresh | O Open | F File | ⌫ Del %d | Esc Back | Q/Ctrl+C Quit%s\n", colorGray, selectCount, colorReset)
|
||||
}
|
||||
} else {
|
||||
if largeFileCount > 0 {
|
||||
fmt.Fprintf(&b, "%s↑↓←→ | Space Select | Enter | R Refresh | O Open | F File | ⌫ Del | T Top %d | Q Quit%s\n", colorGray, largeFileCount, colorReset)
|
||||
fmt.Fprintf(&b, "%s↑↓←→ | Space Select | Enter | R Refresh | O Open | F File | ⌫ Del | T Top %d | Esc Back | Q/Ctrl+C Quit%s\n", colorGray, largeFileCount, colorReset)
|
||||
} else {
|
||||
fmt.Fprintf(&b, "%s↑↓←→ | Space Select | Enter | R Refresh | O Open | F File | ⌫ Del | Q Quit%s\n", colorGray, colorReset)
|
||||
fmt.Fprintf(&b, "%s↑↓←→ | Space Select | Enter | R Refresh | O Open | F File | ⌫ Del | Esc Back | Q/Ctrl+C Quit%s\n", colorGray, colorReset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user