refactor(decnet_web/CanaryTokens): extract list views
Lift the three tab bodies — tokens, blobs, file drops — into their own files. Each takes plain props (data + the operations its rows need), so the page shell stops mixing tab markup with data plumbing. - New CanaryTokens/TokenListView.tsx (text search + state/scope filter selectors + flat row grid; visibleTokens memo lives here now). Exports StateFilter / ScopeFilter union types so the page can declare its filter useState with the right shape. - New CanaryTokens/BlobListView.tsx (delete refused while a token references a blob; ref count badge reuses the disabled button). - New CanaryTokens/FileDropListView.tsx (CLEAR LIST hidden when the local log is empty). - Three companion tests cover empty states, filter behavior, delete refused-vs-allowed, and the per-tab callback wiring. Wiring into CanaryTokens.tsx + the hook lands next.
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { FileDropListView } from './FileDropListView';
|
||||
import type { FileDropEntry } from './FileDropModal';
|
||||
|
||||
const entry = (overrides: Partial<FileDropEntry> = {}): FileDropEntry => ({
|
||||
id: 'fd-1',
|
||||
decky_name: 'decoy-99',
|
||||
topology_id: null,
|
||||
path: '/tmp/payload.bin',
|
||||
size_bytes: 4096,
|
||||
filename: 'payload.bin',
|
||||
mode: 0o644,
|
||||
mtime_offset: 0,
|
||||
dropped_at: '2026-05-09T11:00:00Z',
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe('FileDropListView', () => {
|
||||
it('shows the empty hint when fileDrops is []', () => {
|
||||
render(<FileDropListView fileDrops={[]} onClear={() => {}} />);
|
||||
expect(screen.getByText(/No file drops in this browser yet/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('hides CLEAR LIST when there are no entries', () => {
|
||||
render(<FileDropListView fileDrops={[]} onClear={() => {}} />);
|
||||
expect(screen.queryByText('CLEAR LIST')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders one row per drop with its path + decky name', () => {
|
||||
render(
|
||||
<FileDropListView fileDrops={[entry()]} onClear={() => {}} />,
|
||||
);
|
||||
expect(screen.getByText('decoy-99')).toBeInTheDocument();
|
||||
expect(screen.getByText('/tmp/payload.bin')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('CLEAR LIST invokes onClear', async () => {
|
||||
const onClear = vi.fn();
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<FileDropListView fileDrops={[entry()]} onClear={onClear} />,
|
||||
);
|
||||
await user.click(screen.getByText('CLEAR LIST'));
|
||||
expect(onClear).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user