Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com> Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com> Co-authored-by: opencode <opencode@sst.dev> Co-authored-by: Jay <53023+jayair@users.noreply.github.com> Co-authored-by: David Hill <1879069+iamdavidhill@users.noreply.github.com> Co-authored-by: BB84 <110078428+BB-84C@users.noreply.github.com> Co-authored-by: Aiden Cline <aidenpcline@gmail.com> Co-authored-by: Dax <mail@thdxr.com> Co-authored-by: Brendan Allan <git@brendonovich.dev> Co-authored-by: Jay V <air@live.ca> Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com> Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: Dustin Deus <deusdustin@gmail.com> Co-authored-by: Frank <frank@anoma.ly> Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com> Co-authored-by: Jack <jack@anoma.ly> Co-authored-by: Sebastian <hasta84@gmail.com> Co-authored-by: Jérôme Benoit <jerome.benoit@sap.com> Co-authored-by: Test User <test@test.com> Co-authored-by: Simon Klee <hello@simonklee.dk> Co-authored-by: Rahul A Mistry <149420892+ProdigyRahul@users.noreply.github.com> Co-authored-by: Qiping Li <liqiping1991@gmail.com> Co-authored-by: liqiping <liqiping@msh.team> Co-authored-by: OpeOginni <107570612+OpeOginni@users.noreply.github.com> Co-authored-by: Matthias Reso <13337103+mreso@users.noreply.github.com> Co-authored-by: tobwen <1864057+tobwen@users.noreply.github.com> Co-authored-by: Daniel Polito <danielbpolito@gmail.com> Co-authored-by: opencode <noreply@opencode.ai> Co-authored-by: Devin R Leopold <devin.leopold@gmail.com> Co-authored-by: Zach Bruggeman <mail@bruggie.com> Co-authored-by: Zach Bruggeman <zbruggeman@ramp.com> Co-authored-by: Kit Langton <kit.langton@gmail.com> Co-authored-by: David Siewert <david1gruppenplan@gmail.com> Co-authored-by: Andrei Dziahel <develop7@develop7.info> Co-authored-by: adityachaudhary99 <adityaachaudhary2003@gmail.com> Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com> Co-authored-by: Matt Carey <mcarey@cloudflare.com>
248 lines
9.6 KiB
TypeScript
248 lines
9.6 KiB
TypeScript
import { expect, test } from "bun:test"
|
|
import {
|
|
absoluteTreePath,
|
|
activeTreeNavigation,
|
|
advanceTreePreload,
|
|
nextSuggestionIndex,
|
|
nextTreeScrollTop,
|
|
pickerTreeEntries,
|
|
pickerSearchEntries,
|
|
pickerFileSearchQuery,
|
|
pickerMode,
|
|
preloadTreeDirectories,
|
|
selectedTreePath,
|
|
treeEntries,
|
|
treePathWithin,
|
|
currentPickerSuggestions,
|
|
createDirectorySearch,
|
|
createPriorityTaskQueue,
|
|
displayPickerPath,
|
|
pickerParent,
|
|
pickerRoot,
|
|
pickerAbsoluteInput,
|
|
} from "./directory-picker-domain"
|
|
|
|
test("maps server directory entries into Pierre paths", () => {
|
|
expect(
|
|
treeEntries("src/", [
|
|
{ name: "components", type: "directory" },
|
|
{ name: "index.ts", type: "file" },
|
|
]),
|
|
).toEqual(["src/components/", "src/index.ts"])
|
|
})
|
|
|
|
test("maps Pierre paths back to the selected server root", () => {
|
|
expect(absoluteTreePath("C:/Users/luke", "src/components/")).toBe("C:/Users/luke/src/components")
|
|
expect(absoluteTreePath("C:/", "")).toBe("C:/")
|
|
expect(absoluteTreePath("C:/", "README.md")).toBe("C:/README.md")
|
|
expect(absoluteTreePath("/home/luke", "README.md")).toBe("/home/luke/README.md")
|
|
})
|
|
|
|
test("includes files only when the picker selects files", () => {
|
|
const nodes = [
|
|
{ name: "components", type: "directory" as const },
|
|
{ name: "index.ts", type: "file" as const },
|
|
]
|
|
expect(pickerTreeEntries("", nodes, "directory")).toEqual(["components/"])
|
|
expect(pickerTreeEntries("", nodes, "file")).toEqual(["components/", "index.ts"])
|
|
})
|
|
|
|
test("includes files in file autocomplete while preserving directory navigation", () => {
|
|
const nodes = [
|
|
{ name: "src", absolute: "/repo/src", type: "directory" as const },
|
|
{ name: "README.md", absolute: "/repo/README.md", type: "file" as const },
|
|
]
|
|
expect(pickerSearchEntries(nodes, "directory")).toEqual([nodes[0]])
|
|
expect(pickerSearchEntries(nodes, "file")).toEqual(nodes)
|
|
})
|
|
|
|
test("centralizes file and directory selection policy", () => {
|
|
const file = pickerMode("file", "/repo")
|
|
expect(file.includeFiles).toBeTrue()
|
|
expect(file.selection("/repo/src", "index.ts")).toBe("src/index.ts")
|
|
expect(file.selection("/repo", "src/")).toBeUndefined()
|
|
expect(file.result("/repo", "src/index.ts")).toBe("src/index.ts")
|
|
expect(file.selection("/tmp", "example.txt")).toBeUndefined()
|
|
expect(file.navigation("/repo/src")).toBe("/repo/src")
|
|
expect(file.navigation("/tmp")).toBeUndefined()
|
|
|
|
const directory = pickerMode("directory")
|
|
expect(directory.includeFiles).toBeFalse()
|
|
expect(directory.selection("/repo", "src/")).toBe("/repo/src")
|
|
expect(directory.selection("C:/Users/luke", "repos/")).toBe("C:\\Users\\luke\\repos")
|
|
expect(directory.selection("//Server/Share", "repo/")).toBe("\\\\Server\\Share\\repo")
|
|
expect(directory.navigation("/tmp")).toBe("/tmp")
|
|
expect(directory.result("/repo", "")).toBe("/repo")
|
|
expect(directory.result("C:/Users/luke", "")).toBe("C:\\Users\\luke")
|
|
expect(directory.result("//Server/Share/repo", "")).toBe("\\\\Server\\Share\\repo")
|
|
expect(directory.result("/repo", "", false)).toBeUndefined()
|
|
})
|
|
|
|
test("accepts mutations only from the active navigation", () => {
|
|
expect(activeTreeNavigation(3, 3)).toBeTrue()
|
|
expect(activeTreeNavigation(2, 3)).toBeFalse()
|
|
})
|
|
|
|
test("preserves POSIX case while matching Windows drives case-insensitively", () => {
|
|
expect(treePathWithin("/repo", "/Repo")).toBeFalse()
|
|
expect(treePathWithin("C:/Repo", "c:/repo/src")).toBeTrue()
|
|
expect(treePathWithin("//Server/Share/Repo", "//server/share/repo/src")).toBeTrue()
|
|
expect(pickerMode("file", "//Server/Share/Repo").selection("//server/share/repo/src", "file.ts")).toBe("src/file.ts")
|
|
expect(treePathWithin("/repo", "/repo/../tmp")).toBeFalse()
|
|
expect(treePathWithin("/", "/src")).toBeTrue()
|
|
expect(pickerMode("file", "C:/Repo").selection("c:/repo/src", "file.ts")).toBe("src/file.ts")
|
|
expect(pickerMode("file", "C:/").selection("C:/", "file.ts")).toBe("file.ts")
|
|
})
|
|
|
|
test("displays paths using the selected server path format", () => {
|
|
expect(displayPickerPath("C:/Users/luke/repos", "C:/Users/luke/repos", "C:/Users/luke")).toBe(
|
|
"C:\\Users\\luke\\repos",
|
|
)
|
|
expect(displayPickerPath("C:/Users/luke/repos", "C:\\Users\\luke\\repos", "C:/Users/luke")).toBe(
|
|
"C:\\Users\\luke\\repos",
|
|
)
|
|
expect(displayPickerPath("/home/luke/repos", "repos", "/home/luke")).toBe("~/repos")
|
|
expect(displayPickerPath("/home/luke/repos", "~/repos", "/home/luke")).toBe("~/repos")
|
|
})
|
|
|
|
test("treats the server share prefix as the UNC root", () => {
|
|
expect(pickerRoot("//Server/Share/repo/src")).toBe("//Server/Share")
|
|
expect(pickerRoot("\\\\Server\\Share\\repo\\src")).toBe("//Server/Share")
|
|
expect(pickerParent("//Server/Share")).toBe("//Server/Share")
|
|
expect(pickerParent("//Server/Share/repo")).toBe("//Server/Share")
|
|
})
|
|
|
|
test("resolves relative input against the current picker root", () => {
|
|
expect(pickerAbsoluteInput("src", "/home/luke", "/home/luke/repo")).toBe("/home/luke/repo/src")
|
|
expect(pickerAbsoluteInput("../other", "/home/luke", "/home/luke/repo")).toBe("/home/luke/other")
|
|
expect(pickerAbsoluteInput("~/.config", "/home/luke", "/home/luke/repo")).toBe("/home/luke/.config")
|
|
expect(pickerAbsoluteInput("src", "C:/Users/luke", "C:/Users/luke/repo")).toBe("C:/Users/luke/repo/src")
|
|
})
|
|
|
|
test("exposes autocomplete results only for their source query", () => {
|
|
const result = { query: "/repo/src", items: ["/repo/src/index.ts"] }
|
|
expect(currentPickerSuggestions(result, "/repo/src")).toEqual(result.items)
|
|
expect(currentPickerSuggestions(result, "/repo/test")).toEqual([])
|
|
})
|
|
|
|
test("scopes file autocomplete to the current browser root", () => {
|
|
expect(pickerFileSearchQuery("/home/luke/repos", "/home/luke/repos/src/in", "/home/luke")).toBe("src/in")
|
|
expect(pickerFileSearchQuery("/home/luke", "~/repos/op", "/home/luke")).toBe("repos/op")
|
|
})
|
|
|
|
test("resolves directory autocomplete from the current browser root", async () => {
|
|
const directories: string[] = []
|
|
const sdk = {
|
|
api: {
|
|
file: {
|
|
find: (input: { location?: { directory?: string } }) => {
|
|
directories.push(input.location?.directory ?? "")
|
|
return Promise.resolve({ data: [] })
|
|
},
|
|
},
|
|
},
|
|
} as unknown as Parameters<typeof createDirectorySearch>[0]["sdk"]
|
|
let base = "/repo"
|
|
const search = createDirectorySearch({ sdk, home: () => "/home/luke", base: () => base })
|
|
|
|
await search("components")
|
|
base = "/repo/src"
|
|
await search("components")
|
|
|
|
expect(directories).toEqual(["/repo", "/repo/src"])
|
|
})
|
|
|
|
test("searches from an absolute root without a default base", async () => {
|
|
const directories: string[] = []
|
|
const sdk = {
|
|
api: {
|
|
file: {
|
|
list: (input: { location?: { directory?: string } }) => {
|
|
directories.push(input.location?.directory ?? "")
|
|
return Promise.resolve({
|
|
data: [
|
|
{ path: "Users/", type: "directory" },
|
|
{ path: "tmp/", type: "directory" },
|
|
],
|
|
})
|
|
},
|
|
},
|
|
},
|
|
} as unknown as Parameters<typeof createDirectorySearch>[0]["sdk"]
|
|
const search = createDirectorySearch({ sdk, home: () => "", base: () => undefined })
|
|
|
|
expect(await search("/")).toEqual(["/Users", "/tmp"])
|
|
expect(directories).toEqual(["/"])
|
|
})
|
|
|
|
test("identifies the next directory level to preload", () => {
|
|
expect(
|
|
preloadTreeDirectories("src/", [
|
|
{ name: "components", type: "directory" },
|
|
{ name: "index.ts", type: "file" },
|
|
{ name: "utils", type: "directory" },
|
|
]),
|
|
).toEqual(["src/components/", "src/utils/"])
|
|
})
|
|
|
|
test("advances preloading once for every expanded directory", () => {
|
|
const advanced = new Set<string>()
|
|
expect(advanceTreePreload(advanced, "")).toBeTrue()
|
|
expect(advanceTreePreload(advanced, "")).toBeFalse()
|
|
expect(advanceTreePreload(advanced, "repos/")).toBeTrue()
|
|
})
|
|
|
|
test("limits background tasks and prioritizes newly requested work", async () => {
|
|
const queue = createPriorityTaskQueue<void>(2)
|
|
const first = Promise.withResolvers<void>()
|
|
const second = Promise.withResolvers<void>()
|
|
const started: string[] = []
|
|
let active = 0
|
|
let maximum = 0
|
|
const task = (name: string, blocker?: Promise<void>) => async () => {
|
|
started.push(name)
|
|
active++
|
|
maximum = Math.max(maximum, active)
|
|
await blocker
|
|
active--
|
|
}
|
|
|
|
const running = [
|
|
queue.schedule("first", "background", task("first", first.promise)),
|
|
queue.schedule("second", "background", task("second", second.promise)),
|
|
queue.schedule("preload", "background", task("preload")),
|
|
queue.schedule("opened", "user", task("opened")),
|
|
]
|
|
await Promise.resolve()
|
|
expect(started).toEqual(["first", "second"])
|
|
|
|
first.resolve()
|
|
await running[0]
|
|
await Promise.resolve()
|
|
expect(started).toEqual(["first", "second", "opened"])
|
|
|
|
second.resolve()
|
|
await Promise.all(running)
|
|
expect(started).toEqual(["first", "second", "opened", "preload"])
|
|
expect(maximum).toBe(2)
|
|
})
|
|
|
|
test("clamps bridged tree wheel scrolling", () => {
|
|
expect(nextTreeScrollTop(100, 40, 500, 200)).toBe(140)
|
|
expect(nextTreeScrollTop(10, -40, 500, 200)).toBe(0)
|
|
expect(nextTreeScrollTop(290, 40, 500, 200)).toBe(300)
|
|
})
|
|
|
|
test("wraps autocomplete keyboard navigation", () => {
|
|
expect(nextSuggestionIndex(-1, 1, 4)).toBe(0)
|
|
expect(nextSuggestionIndex(3, 1, 4)).toBe(0)
|
|
expect(nextSuggestionIndex(0, -1, 4)).toBe(3)
|
|
expect(nextSuggestionIndex(0, 1, 0)).toBe(-1)
|
|
})
|
|
|
|
test("returns absolute directories and relative files", () => {
|
|
expect(selectedTreePath("/home/luke/repo", "src/", "directory")).toBe("/home/luke/repo/src")
|
|
expect(selectedTreePath("/home/luke/repo", "src/index.ts", "file")).toBe("src/index.ts")
|
|
expect(selectedTreePath("/home/luke/repo/src", "index.ts", "file", "/home/luke/repo")).toBe("src/index.ts")
|
|
expect(selectedTreePath("/home/luke/repo", "src/", "file")).toBeUndefined()
|
|
})
|