diff --git a/packages/ui/src/stores/useFilesViewTabsStore.ts b/packages/ui/src/stores/useFilesViewTabsStore.ts index 721783b4..0a7080ca 100644 --- a/packages/ui/src/stores/useFilesViewTabsStore.ts +++ b/packages/ui/src/stores/useFilesViewTabsStore.ts @@ -205,12 +205,15 @@ export const useFilesViewTabsStore = create()( return state; } - if (!current.openPaths.includes(normalizedPath) && current.selectedPath !== normalizedPath) { + const comparablePath = toComparablePath(normalizedPath); + const isMatchingPath = (candidate: string) => toComparablePath(candidate) === comparablePath; + const selectedPathMatches = current.selectedPath ? isMatchingPath(current.selectedPath) : false; + if (!current.openPaths.some(isMatchingPath) && !selectedPathMatches) { return state; } - const openPaths = current.openPaths.filter((p) => p !== normalizedPath); - const selectedPath = current.selectedPath === normalizedPath ? (openPaths[0] ?? null) : current.selectedPath; + const openPaths = current.openPaths.filter((p) => !isMatchingPath(p)); + const selectedPath = selectedPathMatches ? (openPaths[0] ?? null) : current.selectedPath; const byRoot = { ...state.byRoot, @@ -238,13 +241,18 @@ export const useFilesViewTabsStore = create()( return state; } - const prefixWithSlash = normalizedPrefix.endsWith('/') ? normalizedPrefix : `${normalizedPrefix}/`; - const openPaths = current.openPaths.filter((p) => p !== normalizedPrefix && !p.startsWith(prefixWithSlash)); + const comparablePrefix = toComparablePath(normalizedPrefix); + const comparablePrefixWithSlash = comparablePrefix.endsWith('/') ? comparablePrefix : `${comparablePrefix}/`; + const isWithinPrefix = (candidate: string) => { + const comparablePath = toComparablePath(candidate); + return comparablePath === comparablePrefix || comparablePath.startsWith(comparablePrefixWithSlash); + }; + const openPaths = current.openPaths.filter((p) => !isWithinPrefix(p)); if (openPaths.length === current.openPaths.length) { return state; } - const selectedPath = current.selectedPath && (current.selectedPath === normalizedPrefix || current.selectedPath.startsWith(prefixWithSlash)) + const selectedPath = current.selectedPath && isWithinPrefix(current.selectedPath) ? (openPaths[0] ?? null) : current.selectedPath; diff --git a/packages/ui/src/stores/useFilesViewTabsStore.windows.test.ts b/packages/ui/src/stores/useFilesViewTabsStore.windows.test.ts new file mode 100644 index 00000000..8be38b4d --- /dev/null +++ b/packages/ui/src/stores/useFilesViewTabsStore.windows.test.ts @@ -0,0 +1,36 @@ +import { beforeEach, describe, expect, test } from 'bun:test'; +import { useFilesViewTabsStore } from './useFilesViewTabsStore'; + +describe('useFilesViewTabsStore Windows paths', () => { + beforeEach(() => { + useFilesViewTabsStore.setState({ byRoot: {} }); + }); + + test('removes open paths by prefix case-insensitively for Windows drive paths', () => { + const root = 'C:/Repo'; + const store = useFilesViewTabsStore.getState(); + + store.addOpenPath(root, 'C:/Repo/src/a.ts'); + store.addOpenPath(root, 'C:/Repo/other.ts'); + store.setSelectedPath(root, 'C:/Repo/src/a.ts'); + store.removeOpenPathsByPrefix(root, 'c:/repo/src'); + + const rootState = useFilesViewTabsStore.getState().byRoot[root]; + expect(rootState?.openPaths).toEqual(['C:/Repo/other.ts']); + expect(rootState?.selectedPath).toBe('C:/Repo/other.ts'); + }); + + test('removes a single open path case-insensitively for Windows drive paths', () => { + const root = 'C:/Repo'; + const store = useFilesViewTabsStore.getState(); + + store.addOpenPath(root, 'C:/Repo/src/a.ts'); + store.addOpenPath(root, 'C:/Repo/other.ts'); + store.setSelectedPath(root, 'C:/Repo/src/a.ts'); + store.removeOpenPath(root, 'c:/repo/src/a.ts'); + + const rootState = useFilesViewTabsStore.getState().byRoot[root]; + expect(rootState?.openPaths).toEqual(['C:/Repo/other.ts']); + expect(rootState?.selectedPath).toBe('C:/Repo/other.ts'); + }); +});