diff --git a/lib/src/material_async_button_theme.dart b/lib/src/material_async_button_theme.dart index ca6f629..7c95596 100644 --- a/lib/src/material_async_button_theme.dart +++ b/lib/src/material_async_button_theme.dart @@ -94,6 +94,12 @@ class AsyncButtonTheme extends ThemeExtension { typedef _LineBoxKey = (TextStyle, TextDirection, TextScaler); final _lineBoxCache = <_LineBoxKey, double>{}; +/// The bounded line-box cache. Exposed only so tests can assert its capacity +/// and LRU eviction. Not part of the consumer-facing API. +@visibleForTesting +Map<(TextStyle, TextDirection, TextScaler), double> get debugLineBoxCache => + _lineBoxCache; + /// The single-line height of the ambient label style at [context] — the /// vertical extent a one-line [Text] occupies here. The default spinner sizes /// to this (the idle content's *line box*, which is taller than the raw diff --git a/test/material_async_button_theme_test.dart b/test/material_async_button_theme_test.dart index 91d246a..7e31e94 100644 --- a/test/material_async_button_theme_test.dart +++ b/test/material_async_button_theme_test.dart @@ -142,4 +142,48 @@ void main() { check(AsyncButtonTheme.empty.transitionBuilder).isNull(); }); }); + + group('AsyncButtonSpinner line box cache', () { + testWidgets( + 'caps at 16 entries, evicts the least recently used, promotes hits', + (tester) async { + debugLineBoxCache.clear(); + addTearDown(debugLineBoxCache.clear); + + Future pumpSpinnerWithFontSize(double fontSize) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DefaultTextStyle( + style: TextStyle(fontSize: fontSize), + child: const AsyncButtonSpinner(), + ), + ), + ), + ); + } + + // Fill the cache to its capacity of 16 with distinct styles. + for (var i = 1; i <= 16; i++) { + await pumpSpinnerWithFontSize(i.toDouble()); + } + check(debugLineBoxCache.length).equals(16); + final firstKey = debugLineBoxCache.keys.first; + check(firstKey.$1.fontSize).equals(1); + + // A 17th style evicts the oldest entry. + await pumpSpinnerWithFontSize(17); + check(debugLineBoxCache.length).equals(16); + check(debugLineBoxCache.containsKey(firstKey)).isFalse(); + + // A hit on the now-oldest entry moves it to the MRU end without + // growing or evicting. + final secondKey = debugLineBoxCache.keys.first; + check(secondKey.$1.fontSize).equals(2); + await pumpSpinnerWithFontSize(2); + check(debugLineBoxCache.length).equals(16); + check(debugLineBoxCache.keys.last).equals(secondKey); + }, + ); + }); }