todo-app/test/images/attachment_links_test.dart
Krzysztof kuhy Rudnicki 9f2ffdb8a9
Some checks failed
dependency freshness / dependencies on newest stable (push) Has been cancelled
file-length / file length <= 250 lines (push) Has been cancelled
md-naming / markdown naming convention (push) Has been cancelled
release-apk / release (APK) (push) Has been cancelled
Attach any file to a note, of any size, not only images
📎 now picks any file (Android: Camera / Gallery / File; desktop: an
unfiltered dialog) and the desktop page accepts any dropped file. Photo and
screenshot formats up to 64 MB are still re-encoded and shown inline;
everything else is stored byte for byte on dufs beside the images and linked
as [name](url), shown as a row with its size that opens in the platform's
app (Android) or a new browser tab (desktop). BACKLOG.md rewrites file links
to ~/data/cloud/todo-images/ like image links.

There is no size cap, so no layer holds a file in memory: uploads and
downloads stream through DufsImageClient, writes land in a .tmp renamed when
whole (an interrupted download no longer leaves a truncated file that looks
done), the desktop page POSTs the browser's own Blob with fetch, and Android's
"File" uses file_picker, because file_selector's Android side reads the whole
file into memory on the UI thread. Verified with a 1 GB file end to end on
desktop (peak RSS +36 MB) and on an API 36 emulator.

The wrapper serves attachments from the origin that holds the GitHub token
and the notes, so only script-free types open inline (nosniff), the rest
download; writes carrying another site's Origin are refused (this also
closes cross-site overwrites of the backup files); and attachment routes run
concurrently so a long upload or a playing video cannot stall the page.

Also: cupertino_icons 2.0.0 (unused, but the freshness gate blocks the
pubspec otherwise), open_file 4.0.0 and file_picker 13.1.0 pinned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 12:17:06 +02:00

62 lines
2.4 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:todo/images/attachment_links.dart';
import 'package:todo/images/attachment_ref.dart';
void main() {
final image = AttachmentRef.tryParse('n1/a.jpg')!;
final pdf = AttachmentRef.tryParse('n1/b.pdf')!;
final other = AttachmentRef.create('n2');
test('attachmentsIn finds images and files, in order, each once', () {
final text =
'hi ${image.imageMarkdown} and\n${pdf.fileMarkdown('b.pdf')}\n'
'${other.imageMarkdown}\n${image.imageMarkdown}\n'
'![x](https://example.com/a.jpg) [y]($kDufsBaseUrl/Keepass/a.pdf)';
final found = attachmentsIn(text);
expect([for (final a in found) a.ref], [image, pdf, other]);
expect([for (final a in found) a.isImage], [true, false, true]);
expect(found[1].name, 'b.pdf');
expect(attachmentsIn('no attachments'), isEmpty);
});
test('an image link is never also read as a file link', () {
final found = attachmentsIn(image.imageMarkdown);
expect(found, hasLength(1));
expect(found.single.isImage, isTrue);
});
test('attachmentRefsIn covers both kinds', () {
expect(
attachmentRefsIn('${image.imageMarkdown}\n${pdf.fileMarkdown('b')}'),
{image, pdf},
);
});
test('attachmentOfLine only matches a line that is just one link', () {
expect(attachmentOfLine(' ${image.imageMarkdown} ')!.ref, image);
expect(attachmentOfLine('![alt text](${image.url})')!.name, 'alt text');
final file = attachmentOfLine(pdf.fileMarkdown('Raport.pdf'))!;
expect(file.ref, pdf);
expect(file.isImage, isFalse);
expect(file.name, 'Raport.pdf');
expect(attachmentOfLine('see ${image.imageMarkdown}'), isNull);
expect(attachmentOfLine('plain'), isNull);
});
test('backlog paths round-trip exactly, images and files alike', () {
final text =
'a\n${image.imageMarkdown}\n${pdf.fileMarkdown('b.pdf')}\n'
'![](https://example.com/x.jpg)';
final disk = toBacklogAttachmentPaths(text);
expect(disk, contains('![]($kBacklogImagesRoot/n1/a.jpg)'));
expect(disk, contains('[b.pdf]($kBacklogImagesRoot/n1/b.pdf)'));
expect(disk, isNot(contains('$kDufsBaseUrl/$kImagesDir')));
expect(fromBacklogAttachmentPaths(disk), text);
});
test('a bare path outside a link is left alone', () {
const text = 'see $kBacklogImagesRoot/n1/b.pdf';
expect(fromBacklogAttachmentPaths(text), text);
});
}