refactor(decnet_web/CanaryTokens): move FileDropModal + LS helpers
Verbatim move of the file-drop modal (~310 LOC) and its localStorage glue (FILEDROP_LS_KEY, FileDropEntry type, loadFileDrops, saveFileDrops) into one file. The list view that shows these entries lives in the page; the persistence layer travels with the writer. - New CanaryTokens/FileDropModal.tsx (modal + LS helpers + entry type) - FileDropModal.test.tsx covers loadFileDrops empty / round-trip / 200-row cap / malformed-JSON, plus modal title rendering, the bypass-warning banner, and CANCEL -> onClose. - CanaryTokens.tsx loses the inline modal + LS glue plus the now-unused imports (useRef/X/AlertTriangle/useEscapeKey/ useFocusTrap, plus BTN_PRIMARY/BTN_GHOST/Field that only the modals consumed).
This commit is contained in:
100
decnet_web/src/components/CanaryTokens/FileDropModal.test.tsx
Normal file
100
decnet_web/src/components/CanaryTokens/FileDropModal.test.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import {
|
||||
FileDropModal, loadFileDrops, saveFileDrops,
|
||||
FILEDROP_LS_KEY, type FileDropEntry,
|
||||
} from './FileDropModal';
|
||||
import type { DeckyOption, TopologyOption } from './types';
|
||||
|
||||
vi.mock('../../hooks/useFocusTrap', () => ({ useFocusTrap: () => {} }));
|
||||
|
||||
const deckies: DeckyOption[] = [{ name: 'decoy-01' }];
|
||||
const topologies: TopologyOption[] = [{ id: 't-1', name: 'corp', status: 'active' }];
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
const sampleEntry = (): FileDropEntry => ({
|
||||
id: 'fd-1',
|
||||
decky_name: 'd',
|
||||
topology_id: null,
|
||||
path: '/tmp/x',
|
||||
size_bytes: 1,
|
||||
filename: 'x',
|
||||
mode: 0o644,
|
||||
mtime_offset: 0,
|
||||
dropped_at: '2026-05-09T11:00:00Z',
|
||||
});
|
||||
|
||||
describe('loadFileDrops / saveFileDrops', () => {
|
||||
it('returns [] when localStorage is empty', () => {
|
||||
expect(loadFileDrops()).toEqual([]);
|
||||
});
|
||||
|
||||
it('round-trips through localStorage', () => {
|
||||
saveFileDrops([sampleEntry()]);
|
||||
const out = loadFileDrops();
|
||||
expect(out).toHaveLength(1);
|
||||
expect(out[0].id).toBe('fd-1');
|
||||
});
|
||||
|
||||
it('caps to 200 entries on save', () => {
|
||||
const many: FileDropEntry[] = Array.from({ length: 250 }, (_, i) => ({
|
||||
...sampleEntry(), id: `fd-${i}`,
|
||||
}));
|
||||
saveFileDrops(many);
|
||||
const stored = JSON.parse(localStorage.getItem(FILEDROP_LS_KEY) ?? '[]');
|
||||
expect(stored).toHaveLength(200);
|
||||
});
|
||||
|
||||
it('returns [] on malformed JSON in storage', () => {
|
||||
localStorage.setItem(FILEDROP_LS_KEY, '{not-an-array');
|
||||
expect(loadFileDrops()).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('FileDropModal', () => {
|
||||
it('renders the title and the Fleet/MazeNET toggle', () => {
|
||||
render(
|
||||
<FileDropModal
|
||||
deckies={deckies}
|
||||
topologies={topologies}
|
||||
onClose={() => {}}
|
||||
onDropped={() => {}}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText('DROP FILE ON DECKY')).toBeInTheDocument();
|
||||
expect(screen.getByText('Fleet')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the bypass-warning banner', () => {
|
||||
render(
|
||||
<FileDropModal
|
||||
deckies={deckies}
|
||||
topologies={topologies}
|
||||
onClose={() => {}}
|
||||
onDropped={() => {}}
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
screen.getByText(/File drops bypass canary instrumentation/),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('CANCEL invokes onClose', async () => {
|
||||
const onClose = vi.fn();
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<FileDropModal
|
||||
deckies={deckies}
|
||||
topologies={topologies}
|
||||
onClose={onClose}
|
||||
onDropped={() => {}}
|
||||
/>,
|
||||
);
|
||||
await user.click(screen.getByText('CANCEL'));
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user