4509a2b6d3
- P5-41/35 useTauriToastActions: native rich-toast click → navigate(path) (opens the room), quick reply → mx.sendMessage(roomId, m.text). The desktop bridge routes message notifications (tag=roomId) to show_rich_toast. - P5-56 useTauriFocusAssist + focusAssistActiveAtom: a native focus-assist-changed event drives the atom, OR'd into the existing quiet-hours gate in ClientNonUIFeatures so notifications+sounds suppress during Windows Focus Assist. - P5-48 recursive folder drag-drop: fileEntries.ts (sync webkitGetAsEntry capture → async batched readEntries traversal, path-prefixed names, 500-file cap) wired into useFileDrop, reusing the existing upload pipeline. +3 unit tests. Hooks no-op in the browser. Gates: tsc/eslint/prettier clean, build OK, 559 tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
71 lines
2.2 KiB
TypeScript
71 lines
2.2 KiB
TypeScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { filesFromEntries } from './fileEntries';
|
|
|
|
const fileEntry = (name: string): FileSystemFileEntry =>
|
|
({
|
|
isFile: true,
|
|
isDirectory: false,
|
|
name,
|
|
file: (success: (file: File) => void) => {
|
|
success(new File(['x'], name, { type: 'text/plain' }));
|
|
},
|
|
}) as unknown as FileSystemFileEntry;
|
|
|
|
/**
|
|
* A directory whose reader yields its children in several batches (mirroring
|
|
* Chromium's `readEntries`, which caps each call) and finally an empty batch.
|
|
*/
|
|
const dirEntry = (name: string, children: FileSystemEntry[]): FileSystemDirectoryEntry => {
|
|
const batches = [children.slice(0, 1), children.slice(1), [] as FileSystemEntry[]];
|
|
return {
|
|
isFile: false,
|
|
isDirectory: true,
|
|
name,
|
|
createReader: () => {
|
|
let call = 0;
|
|
return {
|
|
readEntries: (success: (entries: FileSystemEntry[]) => void) => {
|
|
const batch = batches[call] ?? [];
|
|
call += 1;
|
|
success(batch);
|
|
},
|
|
} as unknown as FileSystemDirectoryReader;
|
|
},
|
|
} as unknown as FileSystemDirectoryEntry;
|
|
};
|
|
|
|
test('filesFromEntries flattens nested folders and prefixes relative paths', async () => {
|
|
const entries: FileSystemEntry[] = [
|
|
fileEntry('top.txt'),
|
|
dirEntry('photos', [
|
|
fileEntry('a.jpg'),
|
|
dirEntry('2024', [fileEntry('b.jpg'), fileEntry('c.jpg')]),
|
|
]),
|
|
];
|
|
|
|
const files = await filesFromEntries(entries);
|
|
const names = files.map((f) => f.name).sort();
|
|
|
|
assert.deepEqual(names, ['photos/2024/b.jpg', 'photos/2024/c.jpg', 'photos/a.jpg', 'top.txt']);
|
|
});
|
|
|
|
test('filesFromEntries reads directory entries in batches until empty', async () => {
|
|
const entries: FileSystemEntry[] = [
|
|
dirEntry('docs', [fileEntry('one.txt'), fileEntry('two.txt')]),
|
|
];
|
|
|
|
const files = await filesFromEntries(entries);
|
|
assert.equal(files.length, 2);
|
|
});
|
|
|
|
test('filesFromEntries respects the maxFiles cap', async () => {
|
|
const entries: FileSystemEntry[] = [
|
|
dirEntry('many', [fileEntry('a.txt'), fileEntry('b.txt')]),
|
|
fileEntry('c.txt'),
|
|
];
|
|
|
|
const files = await filesFromEntries(entries, 2);
|
|
assert.equal(files.length, 2);
|
|
});
|