// Direct tests for InkPictureCache (the P0.5 perf primitive: a committed-ink // ui.Picture is recorded once per revision and replayed, so pinch/pan/live-move // frames never re-rasterize). Verifies build-once caching, revision-keyed // rebuild, LRU eviction, and post-frame disposal. import 'dart:ui' as ui; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:badnote/editor/render/ink_picture_cache.dart'; ui.Picture _pic() { final recorder = ui.PictureRecorder(); Canvas(recorder).drawRect( const Rect.fromLTWH(0, 0, 1, 1), Paint()..color = const Color(0xFF000000), ); return recorder.endRecording(); } void main() { TestWidgetsFlutterBinding.ensureInitialized(); const size = Size(100, 100); test('same (host,revision) builds once, then returns the cached Picture', () { final cache = InkPictureCache(); var builds = 0; ui.Picture build() { builds++; return _pic(); } final p1 = cache.getOrBuild('h', 3, size, build); final p2 = cache.getOrBuild('h', 3, size, build); expect(builds, 1, reason: 'second call is a cache hit'); expect(identical(p1, p2), isTrue); cache.dispose(); }); test('a new revision rebuilds (revision is part of the key)', () { final cache = InkPictureCache(); var builds = 0; cache.getOrBuild('h', 1, size, () { builds++; return _pic(); }); cache.getOrBuild('h', 2, size, () { builds++; return _pic(); }); expect(builds, 2); cache.dispose(); }); test('different host ids are independent', () { final cache = InkPictureCache(); var builds = 0; cache.getOrBuild('a', 1, size, () { builds++; return _pic(); }); cache.getOrBuild('b', 1, size, () { builds++; return _pic(); }); expect(builds, 2); cache.dispose(); }); testWidgets('evicts + disposes the LRU beyond maxSize (post-frame)', (tester) async { final cache = InkPictureCache(maxSize: 2); final first = cache.getOrBuild('a', 1, const Size(100, 100), _pic); cache.getOrBuild('b', 1, const Size(100, 100), _pic); // Re-touch 'a' so 'b' becomes LRU. cache.getOrBuild('a', 1, const Size(100, 100), _pic); // Insert a 3rd → evicts the LRU ('b'); 'a' (first) survives. cache.getOrBuild('c', 1, const Size(100, 100), _pic); // 'a' is still cached → no rebuild. var rebuilt = false; final aAgain = cache.getOrBuild( 'a', 1, const Size(100, 100), () { rebuilt = true; return _pic(); }); expect(rebuilt, isFalse); expect(identical(aAgain, first), isTrue); cache.dispose(); await tester.pump(); // run the deferred disposals }); }