|
| 1 | +package internal |
| 2 | + |
| 3 | +import ( |
| 4 | + "path/filepath" |
| 5 | + "runtime" |
| 6 | + "testing" |
| 7 | + |
| 8 | + tea "github.com/charmbracelet/bubbletea" |
| 9 | + zoxidelib "github.com/lazysegtree/go-zoxide" |
| 10 | + "github.com/stretchr/testify/assert" |
| 11 | + "github.com/stretchr/testify/require" |
| 12 | + |
| 13 | + "github.com/yorukot/superfile/src/internal/common" |
| 14 | + "github.com/yorukot/superfile/src/internal/utils" |
| 15 | +) |
| 16 | + |
| 17 | +func setupProgAndOpenZoxide(t *testing.T, zClient *zoxidelib.Client, dir string) *TeaProg { |
| 18 | + t.Helper() |
| 19 | + common.Config.ZoxideSupport = true |
| 20 | + m := defaultTestModelWithZClient(zClient, dir) |
| 21 | + p := NewTestTeaProgWithEventLoop(t, m) |
| 22 | + |
| 23 | + p.SendKey(common.Hotkeys.OpenZoxide[0]) |
| 24 | + assert.Eventually(t, func() bool { |
| 25 | + return p.getModel().zoxideModal.IsOpen() |
| 26 | + }, DefaultTestTimeout, DefaultTestTick, "Zoxide modal should open") |
| 27 | + return p |
| 28 | +} |
| 29 | + |
| 30 | +func updateCurrentFilePanelDirOfTestModel(t *testing.T, p *TeaProg, dir string) { |
| 31 | + err := p.getModel().updateCurrentFilePanelDir(dir) |
| 32 | + require.NoError(t, err, "Failed to navigate to %s", dir) |
| 33 | + assert.Equal(t, dir, p.getModel().getFocusedFilePanel().location, "Should be in %s after navigation", dir) |
| 34 | +} |
| 35 | + |
| 36 | +func TestZoxide(t *testing.T) { |
| 37 | + zoxideDataDir := t.TempDir() |
| 38 | + zClient, err := zoxidelib.New(zoxidelib.WithDataDir(zoxideDataDir)) |
| 39 | + if err != nil { |
| 40 | + if runtime.GOOS != utils.OsLinux { |
| 41 | + t.Skipf("Skipping zoxide tests in non-Linux because zoxide client cannot be initialized") |
| 42 | + } else { |
| 43 | + t.Fatalf("zoxide initialization failed") |
| 44 | + } |
| 45 | + } |
| 46 | + |
| 47 | + originalZoxideSupport := common.Config.ZoxideSupport |
| 48 | + defer func() { |
| 49 | + common.Config.ZoxideSupport = originalZoxideSupport |
| 50 | + }() |
| 51 | + |
| 52 | + curTestDir := filepath.Join(testDir, "TestZoxide") |
| 53 | + dir1 := filepath.Join(curTestDir, "dir1") |
| 54 | + dir2 := filepath.Join(curTestDir, "dir2") |
| 55 | + dir3 := filepath.Join(curTestDir, "dir3") |
| 56 | + multiSpaceDir := filepath.Join(curTestDir, "test dir") |
| 57 | + utils.SetupDirectories(t, curTestDir, dir1, dir2, dir3, multiSpaceDir) |
| 58 | + |
| 59 | + t.Run("Zoxide tracking and navigation", func(t *testing.T) { |
| 60 | + p := setupProgAndOpenZoxide(t, zClient, dir1) |
| 61 | + updateCurrentFilePanelDirOfTestModel(t, p, dir2) |
| 62 | + updateCurrentFilePanelDirOfTestModel(t, p, dir3) |
| 63 | + |
| 64 | + p.SendKey("dir2") |
| 65 | + assert.Eventually(t, func() bool { |
| 66 | + results := p.getModel().zoxideModal.GetResults() |
| 67 | + return len(results) == 1 && results[0].Path == dir2 |
| 68 | + }, DefaultTestTimeout, DefaultTestTick, "dir2 should be found by zoxide UI search") |
| 69 | + |
| 70 | + // Press enter to navigate to dir2 |
| 71 | + p.SendKey(common.Hotkeys.ConfirmTyping[0]) |
| 72 | + assert.Eventually(t, func() bool { |
| 73 | + return !p.getModel().zoxideModal.IsOpen() |
| 74 | + }, DefaultTestTimeout, DefaultTestTick, "Zoxide modal should close after navigation") |
| 75 | + assert.Equal(t, dir2, p.getModel().getFocusedFilePanel().location, |
| 76 | + "Should navigate back to dir2 after zoxide selection") |
| 77 | + }) |
| 78 | + |
| 79 | + t.Run("Zoxide disabled shows no results", func(t *testing.T) { |
| 80 | + common.Config.ZoxideSupport = false |
| 81 | + m := defaultTestModelWithZClient(zClient, dir1) |
| 82 | + |
| 83 | + TeaUpdate(m, utils.TeaRuneKeyMsg(common.Hotkeys.OpenZoxide[0])) |
| 84 | + assert.True(t, m.zoxideModal.IsOpen(), "Zoxide modal should open even when ZoxideSupport is disabled") |
| 85 | + |
| 86 | + results := m.zoxideModal.GetResults() |
| 87 | + assert.Empty(t, results, "Zoxide modal should show no results when ZoxideSupport is disabled") |
| 88 | + }) |
| 89 | + |
| 90 | + t.Run("Zoxide modal size on window resize", func(t *testing.T) { |
| 91 | + p := setupProgAndOpenZoxide(t, zClient, dir1) |
| 92 | + |
| 93 | + initialWidth := p.getModel().zoxideModal.GetWidth() |
| 94 | + initialMaxHeight := p.getModel().zoxideModal.GetMaxHeight() |
| 95 | + |
| 96 | + p.SendDirectly(tea.WindowSizeMsg{Width: 2 * DefaultTestModelWidth, Height: 2 * DefaultTestModelHeight}) |
| 97 | + |
| 98 | + updatedWidth := p.getModel().zoxideModal.GetWidth() |
| 99 | + updatedMaxHeight := p.getModel().zoxideModal.GetMaxHeight() |
| 100 | + assert.Greater(t, updatedWidth, initialWidth, "Width should increase with larger window") |
| 101 | + assert.Greater(t, updatedMaxHeight, initialMaxHeight, "MaxHeight should increase with larger window") |
| 102 | + }) |
| 103 | + |
| 104 | + t.Run("Zoxide 'z' key suppression on open", func(t *testing.T) { |
| 105 | + p := setupProgAndOpenZoxide(t, zClient, dir1) |
| 106 | + assert.Empty(t, p.getModel().zoxideModal.GetTextInputValue(), |
| 107 | + "The 'z' key should not be added to textInput") |
| 108 | + p.SendKeyDirectly("abc") |
| 109 | + assert.Equal(t, "abc", p.getModel().zoxideModal.GetTextInputValue()) |
| 110 | + }) |
| 111 | + |
| 112 | + t.Run("Multi-space directory name navigation", func(t *testing.T) { |
| 113 | + p := setupProgAndOpenZoxide(t, zClient, dir1) |
| 114 | + |
| 115 | + updateCurrentFilePanelDirOfTestModel(t, p, multiSpaceDir) |
| 116 | + updateCurrentFilePanelDirOfTestModel(t, p, dir1) |
| 117 | + |
| 118 | + p.SendKey(filepath.Base(multiSpaceDir)) |
| 119 | + assert.Eventually(t, func() bool { |
| 120 | + results := p.getModel().zoxideModal.GetResults() |
| 121 | + for _, result := range results { |
| 122 | + if result.Path == multiSpaceDir { |
| 123 | + return true |
| 124 | + } |
| 125 | + } |
| 126 | + return false |
| 127 | + }, DefaultTestTimeout, DefaultTestTick, "Multi-space directory should be found by zoxide") |
| 128 | + |
| 129 | + // Reset textinput via Close-Open |
| 130 | + p.getModel().zoxideModal.Close() |
| 131 | + p.getModel().zoxideModal.Open() |
| 132 | + p.SendKey("di r 1") |
| 133 | + assert.Eventually(t, func() bool { |
| 134 | + results := p.getModel().zoxideModal.GetResults() |
| 135 | + for _, result := range results { |
| 136 | + if result.Path == dir1 { |
| 137 | + return true |
| 138 | + } |
| 139 | + } |
| 140 | + return false |
| 141 | + }, DefaultTestTimeout, DefaultTestTick, "dir1 should be found by zoxide") |
| 142 | + }) |
| 143 | + |
| 144 | + t.Run("Zoxide escape key closes modal", func(t *testing.T) { |
| 145 | + p := setupProgAndOpenZoxide(t, zClient, dir1) |
| 146 | + p.SendKeyDirectly(common.Hotkeys.CancelTyping[0]) |
| 147 | + assert.False(t, p.getModel().zoxideModal.IsOpen(), |
| 148 | + "Zoxide modal should close on escape key") |
| 149 | + }) |
| 150 | +} |
0 commit comments