Skip to content

Commit 531e25d

Browse files
committed
Merge branch 'codex/file-tree-virtualization'
2 parents 70f2fef + 90dc1da commit 531e25d

7 files changed

Lines changed: 525 additions & 248 deletions

File tree

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import React from 'react';
2+
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
3+
import { afterEach, describe, expect, it, vi } from 'vitest';
4+
import FileTree from './FileTree';
5+
import type { FileNode } from '../types';
6+
7+
vi.mock('react-virtuoso', () => ({
8+
Virtuoso: ({ data, itemContent }: any) => (
9+
<div data-testid="virtuoso" data-count={String(data.length)}>
10+
{data.slice(0, 12).map((item: any, index: number) => (
11+
<div key={item.path}>{itemContent(index, item)}</div>
12+
))}
13+
</div>
14+
),
15+
}));
16+
17+
const makeLargeTree = (): FileNode[] => [
18+
{
19+
name: 'src',
20+
path: 'src',
21+
isDirectory: true,
22+
children: Array.from({ length: 200 }, (_, index) => ({
23+
name: `file-${index}.ts`,
24+
path: `src/file-${index}.ts`,
25+
isDirectory: false,
26+
children: [],
27+
status: 'processed' as const,
28+
})),
29+
},
30+
];
31+
32+
afterEach(() => {
33+
cleanup();
34+
});
35+
36+
describe('FileTree virtualization', () => {
37+
it('passes the flattened visible rows into the virtual list instead of mounting the full tree', () => {
38+
render(
39+
<FileTree
40+
nodes={makeLargeTree()}
41+
onFileSelect={vi.fn()}
42+
onDeleteFile={vi.fn()}
43+
onCopyPath={vi.fn()}
44+
onToggleExclude={vi.fn()}
45+
selectedFilePath={null}
46+
showCharCount={false}
47+
/>
48+
);
49+
50+
expect(screen.getByTestId('virtuoso').getAttribute('data-count')).toBe('201');
51+
expect(screen.getByText('file-0.ts')).not.toBeNull();
52+
expect(screen.queryByText('file-199.ts')).toBeNull();
53+
});
54+
55+
it('removes descendant rows when collapsing a directory', () => {
56+
render(
57+
<FileTree
58+
nodes={[
59+
{
60+
name: 'src',
61+
path: 'src',
62+
isDirectory: true,
63+
children: [
64+
{
65+
name: 'index.ts',
66+
path: 'src/index.ts',
67+
isDirectory: false,
68+
children: [],
69+
status: 'processed',
70+
},
71+
],
72+
},
73+
]}
74+
onFileSelect={vi.fn()}
75+
onDeleteFile={vi.fn()}
76+
onCopyPath={vi.fn()}
77+
onToggleExclude={vi.fn()}
78+
selectedFilePath={null}
79+
showCharCount={false}
80+
/>
81+
);
82+
83+
expect(screen.getByText('index.ts')).not.toBeNull();
84+
fireEvent.click(screen.getByText('src'));
85+
expect(screen.queryByText('index.ts')).toBeNull();
86+
});
87+
88+
it('navigates visible rows with the keyboard and opens processed files on Enter', () => {
89+
const onFileSelect = vi.fn();
90+
91+
render(
92+
<FileTree
93+
nodes={[
94+
{
95+
name: 'src',
96+
path: 'src',
97+
isDirectory: true,
98+
children: [
99+
{
100+
name: 'index.ts',
101+
path: 'src/index.ts',
102+
isDirectory: false,
103+
children: [],
104+
status: 'processed',
105+
},
106+
],
107+
},
108+
]}
109+
onFileSelect={onFileSelect}
110+
onDeleteFile={vi.fn()}
111+
onCopyPath={vi.fn()}
112+
onToggleExclude={vi.fn()}
113+
selectedFilePath={null}
114+
showCharCount={false}
115+
/>
116+
);
117+
118+
const tree = screen.getByRole('tree', { name: '资源管理器' });
119+
tree.focus();
120+
121+
fireEvent.keyDown(tree, { key: 'ArrowDown' });
122+
fireEvent.keyDown(tree, { key: 'ArrowDown' });
123+
fireEvent.keyDown(tree, { key: 'Enter' });
124+
125+
expect(onFileSelect).toHaveBeenCalledWith('src/index.ts');
126+
});
127+
});

0 commit comments

Comments
 (0)