test(p0.5): direct InkPictureCache coverage (build-once + LRU)
All checks were successful
CI / Windows build (push) Successful in 11m58s
All checks were successful
CI / Windows build (push) Successful in 11m58s
Fills the gap where the committed-ink Picture cache (the P0.5 perf primitive: record once per revision, replay every frame) was only covered indirectly via StaticInkPainter. Verifies same-(host,revision) builds exactly once then returns the cached Picture; a new revision rebuilds; host ids are independent; LRU eviction beyond maxSize keeps the most-recently-used and disposes the rest post-frame. flutter analyze lib/editor clean; 244/244 tests (+4). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
79
test/ink_picture_cache_test.dart
Normal file
79
test/ink_picture_cache_test.dart
Normal file
@@ -0,0 +1,79 @@
|
||||
// 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
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user