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.
49 lines
1.6 KiB
TypeScript
49 lines
1.6 KiB
TypeScript
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();
|
|
});
|
|
});
|