From efd4cb39019f0fc4ea30c0a5ddcb94c3cc038eef Mon Sep 17 00:00:00 2001 From: gdilla Date: Fri, 13 Mar 2026 17:22:09 -0700 Subject: [PATCH 1/2] feat: add breadcrumb folder navigation to sidebar - Add clickable breadcrumb path bar showing current folder hierarchy - Separate close-sidebar (hide) from close-folder (clear) actions - Add navigateToFolder action for breadcrumb clicks - Add close-folder icon near breadcrumbs - Tests for breadcrumb parsing and navigation Co-Authored-By: Claude Opus 4.6 (1M context) --- src/__tests__/stores/sidebar.test.ts | 101 +++++++++++++++++++++++++- src/components/sidebar/Sidebar.vue | 105 +++++++++++++++++++++++++-- src/stores/sidebar.ts | 63 +++++++++++++++- src/types/filetree.ts | 10 +++ 4 files changed, 265 insertions(+), 14 deletions(-) diff --git a/src/__tests__/stores/sidebar.test.ts b/src/__tests__/stores/sidebar.test.ts index 9384d6d..bf501bd 100644 --- a/src/__tests__/stores/sidebar.test.ts +++ b/src/__tests__/stores/sidebar.test.ts @@ -1,10 +1,14 @@ -import { describe, it, expect, beforeEach } from 'vitest' +import { describe, it, expect, beforeEach, vi } from 'vitest' import { createPinia, setActivePinia } from 'pinia' import { useSidebarStore } from '../../stores/sidebar' +import { invoke } from '@tauri-apps/api/core' + +const mockedInvoke = vi.mocked(invoke) describe('useSidebarStore', () => { beforeEach(() => { setActivePinia(createPinia()) + vi.clearAllMocks() }) it('defaults to not visible with no root path', () => { @@ -85,8 +89,75 @@ describe('useSidebarStore', () => { }) }) + describe('breadcrumbSegments', () => { + it('returns empty array when no root path', () => { + const store = useSidebarStore() + + expect(store.breadcrumbSegments).toEqual([]) + }) + + it('parses absolute path into segments', () => { + const store = useSidebarStore() + + store.rootPath = '/Users/gautambanerjee/projects/Leaf' + + expect(store.breadcrumbSegments).toEqual([ + { name: '~', path: '/Users/gautambanerjee' }, + { name: 'projects', path: '/Users/gautambanerjee/projects' }, + { name: 'Leaf', path: '/Users/gautambanerjee/projects/Leaf' }, + ]) + }) + + it('uses ~ shorthand for home directory on macOS', () => { + const store = useSidebarStore() + + store.rootPath = '/Users/testuser/Documents' + + expect(store.breadcrumbSegments[0]).toEqual({ + name: '~', + path: '/Users/testuser', + }) + expect(store.breadcrumbSegments[1]).toEqual({ + name: 'Documents', + path: '/Users/testuser/Documents', + }) + }) + + it('uses ~ shorthand for home directory on Linux', () => { + const store = useSidebarStore() + + store.rootPath = '/home/user/code/project' + + expect(store.breadcrumbSegments).toEqual([ + { name: '~', path: '/home/user' }, + { name: 'code', path: '/home/user/code' }, + { name: 'project', path: '/home/user/code/project' }, + ]) + }) + + it('handles path without home directory prefix', () => { + const store = useSidebarStore() + + store.rootPath = '/opt/data/myfiles' + + expect(store.breadcrumbSegments).toEqual([ + { name: 'opt', path: '/opt' }, + { name: 'data', path: '/opt/data' }, + { name: 'myfiles', path: '/opt/data/myfiles' }, + ]) + }) + + it('handles home directory root itself', () => { + const store = useSidebarStore() + + store.rootPath = '/Users/testuser' + + expect(store.breadcrumbSegments).toEqual([{ name: '~', path: '/Users/testuser' }]) + }) + }) + describe('closeFolder', () => { - it('resets state and hides sidebar', () => { + it('resets folder state but does not hide sidebar', () => { const store = useSidebarStore() // Simulate an open folder state @@ -99,7 +170,31 @@ describe('useSidebarStore', () => { expect(store.fileTree).toBeNull() expect(store.rootPath).toBeNull() expect(store.error).toBeNull() - expect(store.visible).toBe(false) + // Sidebar stays visible — user can open another folder + expect(store.visible).toBe(true) + }) + }) + + describe('navigateToFolder', () => { + it('calls openFolder with the given path', async () => { + const store = useSidebarStore() + + const mockTree = { + name: 'projects', + path: '/Users/test/projects', + is_dir: true, + children: [], + } + mockedInvoke.mockResolvedValueOnce(mockTree) + + await store.navigateToFolder('/Users/test/projects') + + expect(mockedInvoke).toHaveBeenCalledWith('read_directory_tree', { + path: '/Users/test/projects', + }) + expect(store.rootPath).toBe('/Users/test/projects') + expect(store.fileTree).toEqual(mockTree) + expect(store.visible).toBe(true) }) }) }) diff --git a/src/components/sidebar/Sidebar.vue b/src/components/sidebar/Sidebar.vue index 8c383fd..6887abe 100644 --- a/src/components/sidebar/Sidebar.vue +++ b/src/components/sidebar/Sidebar.vue @@ -91,13 +91,8 @@ const sidebarStyle = computed(() => ({ /> - - + +