Skip to content

Commit a73bca0

Browse files
committed
fix: fix clipboard not auto clear after finish operation
1 parent 0d3f93f commit a73bca0

File tree

4 files changed

+210
-165
lines changed

4 files changed

+210
-165
lines changed

src/internal/handle_file_operation_test.go

Lines changed: 172 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,156 @@ func TestCompressSelectedFiles(t *testing.T) {
167167
})
168168
}
169169

170+
// Helper function to find item index in panel by name
171+
func findItemIndexInPanel(panel *filePanel, itemName string) int {
172+
for i, elem := range panel.element {
173+
if elem.name == itemName {
174+
return i
175+
}
176+
}
177+
return -1
178+
}
179+
180+
// Helper function to setup panel mode and selection
181+
func setupPanelModeAndSelection(t *testing.T, m *model, useSelectMode bool, itemName string, selectedItems []string) {
182+
t.Helper()
183+
panel := m.getFocusedFilePanel()
184+
185+
if useSelectMode {
186+
// Switch to select mode and set selected items
187+
m.changeFilePanelMode()
188+
require.Equal(t, selectMode, panel.panelMode)
189+
panel.selected = selectedItems
190+
} else {
191+
// Find the item in browser mode
192+
itemIndex := findItemIndexInPanel(panel, itemName)
193+
require.NotEqual(t, -1, itemIndex, "%s should be found in panel", itemName)
194+
panel.cursor = itemIndex
195+
}
196+
}
197+
198+
// Helper function to perform copy or cut operation
199+
func performCopyOrCutOperation(t *testing.T, m *model, isCut bool) {
200+
t.Helper()
201+
if isCut {
202+
TeaUpdateWithErrCheck(t, m, utils.TeaRuneKeyMsg(common.Hotkeys.CutItems[0]))
203+
} else {
204+
TeaUpdateWithErrCheck(t, m, utils.TeaRuneKeyMsg(common.Hotkeys.CopyItems[0]))
205+
}
206+
}
207+
208+
// Helper function to verify clipboard state after copy/cut
209+
func verifyClipboardState(t *testing.T, m *model, isCut bool, useSelectMode bool, selectedItemsCount int) {
210+
t.Helper()
211+
assert.Equal(t, isCut, m.copyItems.cut, "Clipboard cut state should match operation")
212+
if useSelectMode {
213+
assert.Len(t, m.copyItems.items, selectedItemsCount, "Clipboard should contain all selected items")
214+
} else {
215+
assert.Len(t, m.copyItems.items, 1, "Clipboard should contain one item")
216+
}
217+
}
218+
219+
// Helper function to navigate to target directory if different from start
220+
func navigateToTargetDir(t *testing.T, m *model, startDir, targetDir string) {
221+
t.Helper()
222+
if targetDir != startDir {
223+
m.updateCurrentFilePanelDir(targetDir)
224+
TeaUpdateWithErrCheck(t, m, nil)
225+
}
226+
}
227+
228+
// Helper function to get original path for existence check
229+
func getOriginalPath(useSelectMode bool, itemName, startDir string) string {
230+
if !useSelectMode && itemName != "" {
231+
return filepath.Join(startDir, itemName)
232+
}
233+
return ""
234+
}
235+
236+
// Helper function to verify file or directory exists
237+
func verifyPathExists(t *testing.T, path, message string) {
238+
t.Helper()
239+
if strings.Contains(path, ".txt") {
240+
assert.FileExists(t, path, message)
241+
} else {
242+
assert.DirExists(t, path, message)
243+
}
244+
}
245+
246+
// Helper function to verify file or directory doesn't exist after cut
247+
func verifyPathNotExistsEventually(t *testing.T, path, message string) {
248+
t.Helper()
249+
assert.Eventually(t, func() bool {
250+
_, err := os.Stat(path)
251+
return os.IsNotExist(err)
252+
}, time.Second, 10*time.Millisecond, message)
253+
}
254+
255+
// Helper function to verify expected destination files exist
256+
func verifyDestinationFiles(t *testing.T, targetDir string, expectedDestFiles []string) {
257+
t.Helper()
258+
for _, expectedFile := range expectedDestFiles {
259+
destPath := filepath.Join(targetDir, expectedFile)
260+
assert.Eventually(t, func() bool {
261+
_, err := os.Stat(destPath)
262+
return err == nil
263+
}, time.Second, 10*time.Millisecond, "%s should exist in destination", expectedFile)
264+
}
265+
}
266+
267+
// Helper function to verify prevented paste results
268+
func verifyPreventedPasteResults(t *testing.T, m *model, originalPath string) {
269+
t.Helper()
270+
if originalPath != "" {
271+
verifyPathExists(t, originalPath, "Original file should still exist when paste is prevented")
272+
}
273+
// Clipboard should not be cleared when paste is prevented
274+
assert.NotEqual(t, 0, len(m.copyItems.items), "Clipboard should not be cleared when paste is prevented")
275+
}
276+
277+
// Helper function to verify successful paste results
278+
func verifySuccessfulPasteResults(t *testing.T, targetDir string, expectedDestFiles []string, originalPath string, shouldOriginalExist bool, shouldClipboardClear bool, m *model) {
279+
t.Helper()
280+
// Verify expected files were created in destination
281+
verifyDestinationFiles(t, targetDir, expectedDestFiles)
282+
283+
// Verify original file existence based on operation type
284+
if originalPath != "" {
285+
if shouldOriginalExist {
286+
verifyPathExists(t, originalPath, "Original file should exist after copy operation")
287+
} else {
288+
verifyPathNotExistsEventually(t, originalPath, "Original file should not exist after cut operation")
289+
}
290+
}
291+
292+
// Verify clipboard state after paste
293+
if shouldClipboardClear {
294+
assert.Eventually(t, func() bool {
295+
return len(m.copyItems.items) == 0
296+
}, time.Second, 10*time.Millisecond, "Clipboard should be cleared after cut operation")
297+
} else {
298+
assert.NotEqual(t, 0, len(m.copyItems.items), "Clipboard should not be cleared after copy operation")
299+
}
300+
}
301+
302+
// Helper function to setup model and perform copy/cut operation
303+
func setupModelAndPerformOperation(t *testing.T, startDir string, useSelectMode bool, itemName string, selectedItems []string, isCut bool) *model {
304+
t.Helper()
305+
m := defaultTestModel(startDir)
306+
TeaUpdateWithErrCheck(t, &m, nil)
307+
308+
setupPanelModeAndSelection(t, &m, useSelectMode, itemName, selectedItems)
309+
performCopyOrCutOperation(t, &m, isCut)
310+
311+
selectedItemsCount := len(selectedItems)
312+
if !useSelectMode {
313+
selectedItemsCount = 1
314+
}
315+
verifyClipboardState(t, &m, isCut, useSelectMode, selectedItemsCount)
316+
317+
return &m
318+
}
319+
170320
func TestPasteItem(t *testing.T) {
171321
curTestDir := t.TempDir()
172322
sourceDir := filepath.Join(curTestDir, "source")
@@ -239,95 +389,22 @@ func TestPasteItem(t *testing.T) {
239389

240390
for _, tt := range testdata {
241391
t.Run(tt.name, func(t *testing.T) {
242-
m := defaultTestModel(tt.startDir)
243-
TeaUpdateWithErrCheck(t, &m, nil)
244-
245-
panel := m.getFocusedFilePanel()
246-
247-
if tt.selectMode {
248-
// Switch to select mode and set selected items
249-
m.changeFilePanelMode()
250-
require.Equal(t, selectMode, panel.panelMode)
251-
panel.selected = tt.selectedItems
252-
} else {
253-
// Find the item in browser mode
254-
itemIndex := -1
255-
for i, elem := range panel.element {
256-
if elem.name == tt.itemName {
257-
itemIndex = i
258-
break
259-
}
260-
}
261-
require.NotEqual(t, -1, itemIndex, "%s should be found in panel", tt.itemName)
262-
panel.cursor = itemIndex
263-
}
264-
265-
// Perform copy or cut operation
266-
if tt.isCut {
267-
TeaUpdateWithErrCheck(t, &m, utils.TeaRuneKeyMsg(common.Hotkeys.CutItems[0]))
268-
} else {
269-
TeaUpdateWithErrCheck(t, &m, utils.TeaRuneKeyMsg(common.Hotkeys.CopyItems[0]))
270-
}
271-
272-
// Verify clipboard state after copy/cut
273-
assert.Equal(t, tt.isCut, m.copyItems.cut, "Clipboard cut state should match operation")
274-
if tt.selectMode {
275-
assert.Len(t, m.copyItems.items, len(tt.selectedItems), "Clipboard should contain all selected items")
276-
} else {
277-
assert.Len(t, m.copyItems.items, 1, "Clipboard should contain one item")
278-
}
392+
m := setupModelAndPerformOperation(t, tt.startDir, tt.selectMode, tt.itemName, tt.selectedItems, tt.isCut)
279393

280394
// Navigate to target directory
281-
if tt.targetDir != tt.startDir {
282-
m.updateCurrentFilePanelDir(tt.targetDir)
283-
TeaUpdateWithErrCheck(t, &m, nil)
284-
}
395+
navigateToTargetDir(t, m, tt.startDir, tt.targetDir)
285396

286397
// Get original file path for existence check
287-
var originalPath string
288-
if !tt.selectMode && tt.itemName != "" {
289-
originalPath = filepath.Join(tt.startDir, tt.itemName)
290-
}
398+
originalPath := getOriginalPath(tt.selectMode, tt.itemName, tt.startDir)
291399

292400
// Perform paste operation
293-
TeaUpdateWithErrCheck(t, &m, utils.TeaRuneKeyMsg(common.Hotkeys.PasteItems[0]))
401+
TeaUpdateWithErrCheck(t, m, utils.TeaRuneKeyMsg(common.Hotkeys.PasteItems[0]))
294402

403+
// Verify results based on whether paste should be prevented
295404
if tt.shouldPreventPaste {
296-
// Verify that paste was prevented (original should still exist, clipboard not cleared)
297-
if originalPath != "" {
298-
if strings.Contains(originalPath, ".txt") {
299-
assert.FileExists(t, originalPath, "Original file should still exist when paste is prevented")
300-
} else {
301-
assert.DirExists(t, originalPath, "Original directory should still exist when paste is prevented")
302-
}
303-
}
304-
// Clipboard should not be cleared when paste is prevented
305-
assert.NotEqual(t, 0, len(m.copyItems.items), "Clipboard should not be cleared when paste is prevented")
405+
verifyPreventedPasteResults(t, m, originalPath)
306406
} else {
307-
// Verify expected files were created in destination
308-
for _, expectedFile := range tt.expectedDestFiles {
309-
destPath := filepath.Join(tt.targetDir, expectedFile)
310-
assert.Eventually(t, func() bool {
311-
_, err := os.Stat(destPath)
312-
return err == nil
313-
}, time.Second, 10*time.Millisecond, "%s should exist in destination", expectedFile)
314-
}
315-
316-
// Verify original file existence based on operation type
317-
if originalPath != "" {
318-
if tt.shouldOriginalExist {
319-
if strings.Contains(originalPath, ".txt") {
320-
assert.FileExists(t, originalPath, "Original file should exist after copy operation")
321-
} else {
322-
assert.DirExists(t, originalPath, "Original directory should exist after copy operation")
323-
}
324-
} else {
325-
assert.Eventually(t, func() bool {
326-
_, err := os.Stat(originalPath)
327-
return os.IsNotExist(err)
328-
}, time.Second, 10*time.Millisecond, "Original file should not exist after cut operation")
329-
}
330-
}
407+
verifySuccessfulPasteResults(t, tt.targetDir, tt.expectedDestFiles, originalPath, tt.shouldOriginalExist, tt.shouldClipboardClear, m)
331408
}
332409
})
333410
}
@@ -363,40 +440,18 @@ func TestPasteItem(t *testing.T) {
363440
multiFile2 := filepath.Join(sourceDir, "multi2.txt")
364441
setupFiles(t, multiFile1, multiFile2)
365442

366-
m := defaultTestModel(sourceDir)
367-
TeaUpdateWithErrCheck(t, &m, nil)
368-
369-
// Switch to select mode
370-
m.changeFilePanelMode()
371-
require.Equal(t, selectMode, m.getFocusedFilePanel().panelMode)
372-
373-
// Select multiple files
374-
panel := m.getFocusedFilePanel()
375-
panel.selected = []string{multiFile1, multiFile2}
376-
377-
// Copy selected items
378-
TeaUpdateWithErrCheck(t, &m, utils.TeaRuneKeyMsg(common.Hotkeys.CopyItems[0]))
379-
380-
// Verify clipboard
381-
assert.False(t, m.copyItems.cut, "Should be copy operation")
382-
assert.Len(t, m.copyItems.items, 2, "Should have two items in clipboard")
443+
selectedItems := []string{multiFile1, multiFile2}
444+
m := setupModelAndPerformOperation(t, sourceDir, true, "", selectedItems, false)
383445

384446
// Navigate to destination
385-
m.updateCurrentFilePanelDir(destDir)
386-
TeaUpdateWithErrCheck(t, &m, nil)
447+
navigateToTargetDir(t, m, sourceDir, destDir)
387448

388449
// Paste items
389-
TeaUpdateWithErrCheck(t, &m, utils.TeaRuneKeyMsg(common.Hotkeys.PasteItems[0]))
450+
TeaUpdateWithErrCheck(t, m, utils.TeaRuneKeyMsg(common.Hotkeys.PasteItems[0]))
390451

391452
// Verify both files were copied
392-
destMulti1 := filepath.Join(destDir, "multi1.txt")
393-
destMulti2 := filepath.Join(destDir, "multi2.txt")
394-
395-
assert.Eventually(t, func() bool {
396-
_, err1 := os.Stat(destMulti1)
397-
_, err2 := os.Stat(destMulti2)
398-
return err1 == nil && err2 == nil
399-
}, time.Second, 10*time.Millisecond, "Both files should be copied to destination")
453+
expectedDestFiles := []string{"multi1.txt", "multi2.txt"}
454+
verifyDestinationFiles(t, destDir, expectedDestFiles)
400455
})
401456

402457
t.Run("Cut into Subdirectory Prevention", func(t *testing.T) {
@@ -407,28 +462,11 @@ func TestPasteItem(t *testing.T) {
407462
setupFiles(t, testDirFile)
408463

409464
// Test the logic that prevents cutting a directory into its subdirectory
410-
m := defaultTestModel(sourceDir)
411-
TeaUpdateWithErrCheck(t, &m, nil)
412-
413-
// Find testsubdir and cut it
414-
panel := m.getFocusedFilePanel()
415-
subdirIndex := -1
416-
for i, elem := range panel.element {
417-
if elem.name == "testsubdir" {
418-
subdirIndex = i
419-
break
420-
}
421-
}
422-
require.NotEqual(t, -1, subdirIndex, "testsubdir should be found in panel")
423-
panel.cursor = subdirIndex
424-
425-
// Cut the directory
426-
TeaUpdateWithErrCheck(t, &m, utils.TeaRuneKeyMsg(common.Hotkeys.CutItems[0]))
465+
m := setupModelAndPerformOperation(t, sourceDir, false, "testsubdir", nil, true)
427466

428467
// Navigate into the subdirectory and try to paste there (should be prevented)
429-
m.updateCurrentFilePanelDir(testSubDir)
430-
TeaUpdateWithErrCheck(t, &m, nil)
431-
TeaUpdateWithErrCheck(t, &m, utils.TeaRuneKeyMsg(common.Hotkeys.PasteItems[0]))
468+
navigateToTargetDir(t, m, sourceDir, testSubDir)
469+
TeaUpdateWithErrCheck(t, m, utils.TeaRuneKeyMsg(common.Hotkeys.PasteItems[0]))
432470

433471
// Directory should still exist in original location after prevention
434472
assert.DirExists(t, testSubDir, "Directory should still exist after failed paste into subdirectory")
@@ -439,43 +477,19 @@ func TestPasteItem(t *testing.T) {
439477
dupFile := filepath.Join(sourceDir, "duplicate.txt")
440478
setupFiles(t, dupFile)
441479

442-
m := defaultTestModel(sourceDir)
443-
TeaUpdateWithErrCheck(t, &m, nil)
444-
445-
// Find and copy the file
446-
panel := m.getFocusedFilePanel()
447-
dupIndex := -1
448-
for i, elem := range panel.element {
449-
if elem.name == "duplicate.txt" {
450-
dupIndex = i
451-
break
452-
}
453-
}
454-
require.NotEqual(t, -1, dupIndex, "duplicate.txt should be found in panel")
455-
panel.cursor = dupIndex
456-
457-
TeaUpdateWithErrCheck(t, &m, utils.TeaRuneKeyMsg(common.Hotkeys.CopyItems[0]))
480+
m := setupModelAndPerformOperation(t, sourceDir, false, "duplicate.txt", nil, false)
458481

459482
// Navigate to destination and paste
460-
m.updateCurrentFilePanelDir(destDir)
461-
TeaUpdateWithErrCheck(t, &m, nil)
462-
TeaUpdateWithErrCheck(t, &m, utils.TeaRuneKeyMsg(common.Hotkeys.PasteItems[0]))
483+
navigateToTargetDir(t, m, sourceDir, destDir)
484+
TeaUpdateWithErrCheck(t, m, utils.TeaRuneKeyMsg(common.Hotkeys.PasteItems[0]))
463485

464486
// Verify first copy
465-
destDup1 := filepath.Join(destDir, "duplicate.txt")
466-
assert.Eventually(t, func() bool {
467-
_, err := os.Stat(destDup1)
468-
return err == nil
469-
}, time.Second, 10*time.Millisecond, "First copy should succeed")
487+
verifyDestinationFiles(t, destDir, []string{"duplicate.txt"})
470488

471489
// Paste again to test duplicate handling
472-
TeaUpdateWithErrCheck(t, &m, utils.TeaRuneKeyMsg(common.Hotkeys.PasteItems[0]))
490+
TeaUpdateWithErrCheck(t, m, utils.TeaRuneKeyMsg(common.Hotkeys.PasteItems[0]))
473491

474492
// Verify duplicate file with different name
475-
destDup2 := filepath.Join(destDir, "duplicate(1).txt")
476-
assert.Eventually(t, func() bool {
477-
_, err := os.Stat(destDup2)
478-
return err == nil
479-
}, time.Second, 10*time.Millisecond, "Duplicate file should be created with (1) suffix")
493+
verifyDestinationFiles(t, destDir, []string{"duplicate(1).txt"})
480494
})
481495
}

0 commit comments

Comments
 (0)