fix(tui): interaction improvements to diff viewer (#28851)
This commit is contained in:
@@ -2,12 +2,17 @@ import { describe, expect, test } from "bun:test"
|
||||
import {
|
||||
allExpandedFileTreeDirectories,
|
||||
buildFileTree,
|
||||
fileTreeFileSelection,
|
||||
flattenFileTree,
|
||||
moveFileTreeSelection,
|
||||
moveFileTreeSelectionToFirstChild,
|
||||
moveFileTreeSelectionToFile,
|
||||
moveFileTreeSelectionToParent,
|
||||
movePatchFileIndex,
|
||||
orderedPatchFileIndexes,
|
||||
relativePatchFileIndexFromViewport,
|
||||
setFileTreeDirectoryExpanded,
|
||||
singlePatchFileIndex,
|
||||
toggleFileTreeDirectory,
|
||||
} from "../../../src/cli/cmd/tui/feature-plugins/system/diff-viewer-file-tree-utils"
|
||||
|
||||
@@ -233,6 +238,100 @@ describe("diff viewer file tree utilities", () => {
|
||||
expect(moveFileTreeSelectionToFile(rows, readme.id, 1)).toBe(readme.id)
|
||||
})
|
||||
|
||||
test("selects a file tree node and expands its parents for a patch file", () => {
|
||||
const tree = buildFileTree([
|
||||
{ file: "src/config/tui.ts" },
|
||||
{ file: "src/session/index.ts" },
|
||||
{ file: "README.md" },
|
||||
])
|
||||
const selection = fileTreeFileSelection(tree, 1)
|
||||
|
||||
expect(selection?.highlightedNode).toBe(tree.nodes.find((node) => node.kind === "file" && node.name === "index.ts")?.id)
|
||||
expect([...selection!.expandedNodes].map((id) => tree.nodes[id]!.name)).toEqual(["session", "src"])
|
||||
expect(fileTreeFileSelection(tree, 99)).toBeUndefined()
|
||||
})
|
||||
|
||||
test("prefers the selected file when choosing the single patch file", () => {
|
||||
expect(singlePatchFileIndex(2, 1, 0, 3)).toBe(2)
|
||||
expect(singlePatchFileIndex(undefined, 1, 0, 3)).toBe(1)
|
||||
expect(singlePatchFileIndex(undefined, undefined, 0, 3)).toBe(0)
|
||||
expect(singlePatchFileIndex(undefined, undefined, undefined, 3)).toBe(3)
|
||||
})
|
||||
|
||||
test("orders patches by the flattened file tree order", () => {
|
||||
const rows = flattenFileTree(
|
||||
buildFileTree([
|
||||
{ file: "src/dir-8/juniper-4.ts" },
|
||||
{ file: "src/dir-8/harbor-94.ts" },
|
||||
{ file: "src/dir-8/cedar-16.ts" },
|
||||
]),
|
||||
)
|
||||
|
||||
expect(orderedPatchFileIndexes(rows)).toEqual([2, 1, 0])
|
||||
})
|
||||
|
||||
test("moves patch selection through the ordered patch file indexes", () => {
|
||||
const fileIndexes = [2, 1, 0]
|
||||
|
||||
expect(movePatchFileIndex(fileIndexes, undefined, 1)).toBe(2)
|
||||
expect(movePatchFileIndex(fileIndexes, undefined, -1)).toBe(0)
|
||||
expect(movePatchFileIndex(fileIndexes, 2, 1)).toBe(1)
|
||||
expect(movePatchFileIndex(fileIndexes, 1, -1)).toBe(2)
|
||||
expect(movePatchFileIndex(fileIndexes, 0, 1)).toBe(0)
|
||||
expect(movePatchFileIndex(fileIndexes, 99, 1)).toBe(2)
|
||||
expect(movePatchFileIndex([], undefined, 1)).toBeUndefined()
|
||||
})
|
||||
|
||||
test("moves to the next visible patch title below the viewport", () => {
|
||||
expect(
|
||||
relativePatchFileIndexFromViewport(
|
||||
[
|
||||
{ fileIndex: 0, titleContentY: 0 },
|
||||
{ fileIndex: 1, titleContentY: 30 },
|
||||
{ fileIndex: 2, titleContentY: 60 },
|
||||
],
|
||||
10,
|
||||
1,
|
||||
),
|
||||
).toBe(1)
|
||||
expect(
|
||||
relativePatchFileIndexFromViewport(
|
||||
[
|
||||
{ fileIndex: 0, titleContentY: 0 },
|
||||
{ fileIndex: 1, titleContentY: 30 },
|
||||
{ fileIndex: 2, titleContentY: 60 },
|
||||
],
|
||||
30,
|
||||
1,
|
||||
),
|
||||
).toBe(2)
|
||||
})
|
||||
|
||||
test("moves to the previous visible patch title above the viewport", () => {
|
||||
expect(
|
||||
relativePatchFileIndexFromViewport(
|
||||
[
|
||||
{ fileIndex: 0, titleContentY: 0 },
|
||||
{ fileIndex: 1, titleContentY: 30 },
|
||||
{ fileIndex: 2, titleContentY: 60 },
|
||||
],
|
||||
50,
|
||||
-1,
|
||||
),
|
||||
).toBe(1)
|
||||
expect(
|
||||
relativePatchFileIndexFromViewport(
|
||||
[
|
||||
{ fileIndex: 0, titleContentY: 0 },
|
||||
{ fileIndex: 1, titleContentY: 30 },
|
||||
{ fileIndex: 2, titleContentY: 60 },
|
||||
],
|
||||
30,
|
||||
-1,
|
||||
),
|
||||
).toBe(0)
|
||||
})
|
||||
|
||||
test("toggles only selected directory expansion", () => {
|
||||
const tree = buildFileTree([{ file: "src/config/tui.ts" }, { file: "README.md" }])
|
||||
const src = tree.nodes.find((node) => node.kind === "directory" && node.name === "src")!
|
||||
|
||||
@@ -155,7 +155,7 @@ async function renderFrame(component: () => JSX.Element) {
|
||||
const app = await testRender(() => withTheme(component), { width: 40, height: 10 })
|
||||
try {
|
||||
await renderOnceSettled(app)
|
||||
return app.captureCharFrame()
|
||||
return await captureSettledFrame(app)
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
}
|
||||
@@ -167,6 +167,16 @@ async function renderOnceSettled(app: Awaited<ReturnType<typeof testRender>>) {
|
||||
await app.renderOnce()
|
||||
}
|
||||
|
||||
async function captureSettledFrame(app: Awaited<ReturnType<typeof testRender>>) {
|
||||
for (let attempt = 0; attempt < 5; attempt++) {
|
||||
const frame = app.captureCharFrame()
|
||||
if (frame.trim().length > 0) return frame
|
||||
await new Promise((resolve) => setTimeout(resolve, 25))
|
||||
await app.renderOnce()
|
||||
}
|
||||
return app.captureCharFrame()
|
||||
}
|
||||
|
||||
function withTheme(component: () => JSX.Element) {
|
||||
return (
|
||||
<TuiConfigProvider config={createTuiResolvedConfig()}>
|
||||
|
||||
Reference in New Issue
Block a user