feat(import): one Import-file entry + vault notebooks
All checks were successful
CI / Windows build (push) Successful in 14m14s
All checks were successful
CI / Windows build (push) Successful in 14m14s
Phase 3. Import becomes a single top-level action beside "Create notebook" and the library is vault-backed. - VaultService.createNotebook copies a picked file into a fresh (de-duplicated) notebook folder under the vault; its sidecar lives beside it, so annotations travel with the file. - Home screen: one "Import file" action with a multi-extension picker (pdf / docx / pptx); routes to the editor by extension. - The document list is now a vault scan (folders with a source file), not the SQLite documents table — no cache, always correct. - PPTX soffice detection fix; DOCX convert-on-import is best-effort and fails gracefully when LibreOffice is unavailable. analyze clean, tests green.
This commit is contained in:
@@ -78,4 +78,139 @@ void main() {
|
||||
await vault.setVaultRoot('');
|
||||
expect(await vault.vaultRootValid(), isFalse);
|
||||
});
|
||||
|
||||
group('createNotebook', () {
|
||||
late Directory srcDir;
|
||||
|
||||
setUp(() async {
|
||||
srcDir = await Directory.systemTemp.createTemp('vault_src');
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
if (await srcDir.exists()) await srcDir.delete(recursive: true);
|
||||
});
|
||||
|
||||
Future<String> makeSource(String name, [String content = 'pdf-bytes']) async {
|
||||
final f = File('${srcDir.path}${Platform.pathSeparator}$name');
|
||||
await f.writeAsString(content);
|
||||
return f.path;
|
||||
}
|
||||
|
||||
test('creates folder, copies file, returns the in-vault path', () async {
|
||||
final vault = await makeService();
|
||||
await vault.setVaultRoot(tempDir.path);
|
||||
final src = await makeSource('Calculus Lecture 3.pdf', 'hello');
|
||||
|
||||
final vaultPath = await vault.createNotebook(src);
|
||||
|
||||
// Returned path is inside the vault, in a folder named from the file.
|
||||
final expectedFolder =
|
||||
'${tempDir.path}${Platform.pathSeparator}Calculus Lecture 3';
|
||||
expect(Directory(expectedFolder).existsSync(), isTrue);
|
||||
expect(
|
||||
vaultPath,
|
||||
'$expectedFolder${Platform.pathSeparator}Calculus Lecture 3.pdf',
|
||||
);
|
||||
// The file was copied with its contents intact.
|
||||
expect(File(vaultPath).existsSync(), isTrue);
|
||||
expect(File(vaultPath).readAsStringSync(), 'hello');
|
||||
// The original is untouched (copy, not move).
|
||||
expect(File(src).existsSync(), isTrue);
|
||||
});
|
||||
|
||||
test('de-duplicates the folder name on collision', () async {
|
||||
final vault = await makeService();
|
||||
await vault.setVaultRoot(tempDir.path);
|
||||
|
||||
final a = await makeSource('Notes.pdf', 'A');
|
||||
final firstPath = await vault.createNotebook(a);
|
||||
expect(
|
||||
firstPath,
|
||||
'${tempDir.path}${Platform.pathSeparator}Notes${Platform.pathSeparator}Notes.pdf',
|
||||
);
|
||||
|
||||
// Re-import a file with the same basename → suffixed folder.
|
||||
final b = await makeSource('Notes.pdf', 'B');
|
||||
final secondPath = await vault.createNotebook(b);
|
||||
expect(
|
||||
secondPath,
|
||||
'${tempDir.path}${Platform.pathSeparator}Notes 2${Platform.pathSeparator}Notes.pdf',
|
||||
);
|
||||
expect(File(secondPath).readAsStringSync(), 'B');
|
||||
// First copy still intact.
|
||||
expect(File(firstPath).readAsStringSync(), 'A');
|
||||
});
|
||||
|
||||
test('throws when no vault root is set', () async {
|
||||
final vault = await makeService();
|
||||
final src = await makeSource('x.pdf');
|
||||
expect(() => vault.createNotebook(src), throwsStateError);
|
||||
});
|
||||
});
|
||||
|
||||
group('scanNotebooks', () {
|
||||
Future<String> writeFile(String path, [String content = 'x']) async {
|
||||
final f = File(path);
|
||||
await f.create(recursive: true);
|
||||
await f.writeAsString(content);
|
||||
return f.path;
|
||||
}
|
||||
|
||||
test('empty / missing vault yields an empty list', () async {
|
||||
final vault = await makeService();
|
||||
// No root set.
|
||||
expect(await vault.scanNotebooks(), isEmpty);
|
||||
// Root set but empty.
|
||||
await vault.setVaultRoot(tempDir.path);
|
||||
expect(await vault.scanNotebooks(), isEmpty);
|
||||
});
|
||||
|
||||
test('lists notebook folders that contain a source file', () async {
|
||||
final vault = await makeService();
|
||||
await vault.setVaultRoot(tempDir.path);
|
||||
final sep = Platform.pathSeparator;
|
||||
|
||||
// A PDF notebook with a sidecar.
|
||||
await writeFile('${tempDir.path}${sep}Lecture${sep}Lecture.pdf');
|
||||
await writeFile(
|
||||
'${tempDir.path}${sep}Lecture${sep}Lecture.pdf.badnote.json',
|
||||
'{}',
|
||||
);
|
||||
// A PPTX notebook without a sidecar.
|
||||
await writeFile('${tempDir.path}${sep}Deck${sep}Deck.pptx');
|
||||
// A hidden vault-metadata folder is skipped.
|
||||
await writeFile('${tempDir.path}$sep.badnote${sep}vault.json', '{}');
|
||||
// A folder with no importable file is skipped.
|
||||
await writeFile('${tempDir.path}${sep}Empty${sep}readme.txt');
|
||||
|
||||
final notebooks = await vault.scanNotebooks();
|
||||
final byName = {for (final n in notebooks) n.filename: n};
|
||||
|
||||
expect(byName.keys, containsAll(['Lecture.pdf', 'Deck.pptx']));
|
||||
expect(notebooks.length, 2);
|
||||
|
||||
expect(byName['Lecture.pdf']!.docType, 'pdf');
|
||||
expect(byName['Lecture.pdf']!.hasSidecar, isTrue);
|
||||
expect(byName['Deck.pptx']!.docType, 'pptx');
|
||||
expect(byName['Deck.pptx']!.hasSidecar, isFalse);
|
||||
});
|
||||
|
||||
test('a created notebook is discoverable by the scan', () async {
|
||||
final vault = await makeService();
|
||||
await vault.setVaultRoot(tempDir.path);
|
||||
final srcDir = await Directory.systemTemp.createTemp('vault_scan_src');
|
||||
try {
|
||||
final src = '${srcDir.path}${Platform.pathSeparator}Doc.pdf';
|
||||
await File(src).writeAsString('bytes');
|
||||
await vault.createNotebook(src);
|
||||
|
||||
final notebooks = await vault.scanNotebooks();
|
||||
expect(notebooks.length, 1);
|
||||
expect(notebooks.first.filename, 'Doc.pdf');
|
||||
expect(notebooks.first.docType, 'pdf');
|
||||
} finally {
|
||||
await srcDir.delete(recursive: true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user