From d560b795a749b376d4bc58ccdfb85b404b0a6828 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 22 Aug 2026 11:58:01 +0000 Subject: [PATCH 1/3] test: add cache eviction test for _lineBoxCache Adds an edge case widget test in material_async_button_theme_test.dart to verify that AsyncButtonSpinner's line box cache stays capped at a maximum capacity of 16 entries and evicts the oldest entry when a 17th distinct text style is evaluated. Exposes debugLineBoxCache with @visibleForTesting for inspection in tests. Co-authored-by: esenmx <43244505+esenmx@users.noreply.github.com> --- lib/src/material_async_button_theme.dart | 4 ++ test/material_async_button_theme_test.dart | 51 ++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/lib/src/material_async_button_theme.dart b/lib/src/material_async_button_theme.dart index ca6f629..5cb7fa7 100644 --- a/lib/src/material_async_button_theme.dart +++ b/lib/src/material_async_button_theme.dart @@ -94,6 +94,10 @@ class AsyncButtonTheme extends ThemeExtension { typedef _LineBoxKey = (TextStyle, TextDirection, TextScaler); final _lineBoxCache = <_LineBoxKey, double>{}; +/// Exposes the line box cache for testing cache limits and eviction. +@visibleForTesting +Map 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..ba621d4 100644 --- a/test/material_async_button_theme_test.dart +++ b/test/material_async_button_theme_test.dart @@ -141,5 +141,56 @@ void main() { check(AsyncButtonTheme.empty.loadingBuilder).isNull(); check(AsyncButtonTheme.empty.transitionBuilder).isNull(); }); + + testWidgets( + 'AsyncButtonSpinner line box cache evicts oldest entry when ' + 'capacity exceeds 16', + (tester) async { + debugLineBoxCache.clear(); + + Future pumpSpinnerWithFontSize(double fontSize) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DefaultTextStyle( + style: TextStyle(fontSize: fontSize), + child: const AsyncButtonSpinner(), + ), + ), + ), + ); + } + + // Pump 16 distinct text styles to fill cache to its max capacity of 16. + for (var i = 1; i <= 16; i++) { + await pumpSpinnerWithFontSize(i.toDouble()); + } + + check(debugLineBoxCache.length).equals(16); + + // Identify the key for fontSize 1 (the first inserted entry). + final firstKey = debugLineBoxCache.keys.first; + check(debugLineBoxCache.containsKey(firstKey)).isTrue(); + + // Pump 17th distinct text style. This should evict oldest entry. + await pumpSpinnerWithFontSize(17); + + check(debugLineBoxCache.length).equals(16); + check(debugLineBoxCache.containsKey(firstKey)).isFalse(); + + // Re-querying an existing cached style (e.g. fontSize 2) promotes it + // and does not expand cache length or evict any entry. + final secondKey = debugLineBoxCache.keys.first; // fontSize 2 + await pumpSpinnerWithFontSize(2); + + check(debugLineBoxCache.length).equals(16); + check(debugLineBoxCache.containsKey(secondKey)).isTrue(); + // secondKey was re-inserted at the end of LRU cache, so it is no + // longer the first key. + check(debugLineBoxCache.keys.first).not((it) => it.equals(secondKey)); + + debugLineBoxCache.clear(); + }, + ); }); } From 18b8d93f713a221a3c6dad486c763c6088a0d3ac Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 22 Aug 2026 12:01:55 +0000 Subject: [PATCH 2/3] test: add cache eviction test for _lineBoxCache Formats material_async_button_theme_test.dart with `dart format` to resolve CI format check failure and adds unit test for _lineBoxCache eviction when capacity exceeds 16 entries. Co-authored-by: esenmx <43244505+esenmx@users.noreply.github.com> --- test/material_async_button_theme_test.dart | 77 +++++++++++----------- 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/test/material_async_button_theme_test.dart b/test/material_async_button_theme_test.dart index ba621d4..13e5d39 100644 --- a/test/material_async_button_theme_test.dart +++ b/test/material_async_button_theme_test.dart @@ -142,55 +142,52 @@ void main() { check(AsyncButtonTheme.empty.transitionBuilder).isNull(); }); - testWidgets( - 'AsyncButtonSpinner line box cache evicts oldest entry when ' - 'capacity exceeds 16', - (tester) async { - debugLineBoxCache.clear(); - - Future pumpSpinnerWithFontSize(double fontSize) async { - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DefaultTextStyle( - style: TextStyle(fontSize: fontSize), - child: const AsyncButtonSpinner(), - ), + testWidgets('AsyncButtonSpinner line box cache evicts oldest entry when ' + 'capacity exceeds 16', (tester) async { + debugLineBoxCache.clear(); + + Future pumpSpinnerWithFontSize(double fontSize) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DefaultTextStyle( + style: TextStyle(fontSize: fontSize), + child: const AsyncButtonSpinner(), ), ), - ); - } + ), + ); + } - // Pump 16 distinct text styles to fill cache to its max capacity of 16. - for (var i = 1; i <= 16; i++) { - await pumpSpinnerWithFontSize(i.toDouble()); - } + // Pump 16 distinct text styles to fill cache to its max capacity of 16. + for (var i = 1; i <= 16; i++) { + await pumpSpinnerWithFontSize(i.toDouble()); + } - check(debugLineBoxCache.length).equals(16); + check(debugLineBoxCache.length).equals(16); - // Identify the key for fontSize 1 (the first inserted entry). - final firstKey = debugLineBoxCache.keys.first; - check(debugLineBoxCache.containsKey(firstKey)).isTrue(); + // Identify the key for fontSize 1 (the first inserted entry). + final firstKey = debugLineBoxCache.keys.first; + check(debugLineBoxCache.containsKey(firstKey)).isTrue(); - // Pump 17th distinct text style. This should evict oldest entry. - await pumpSpinnerWithFontSize(17); + // Pump 17th distinct text style. This should evict oldest entry. + await pumpSpinnerWithFontSize(17); - check(debugLineBoxCache.length).equals(16); - check(debugLineBoxCache.containsKey(firstKey)).isFalse(); + check(debugLineBoxCache.length).equals(16); + check(debugLineBoxCache.containsKey(firstKey)).isFalse(); - // Re-querying an existing cached style (e.g. fontSize 2) promotes it - // and does not expand cache length or evict any entry. - final secondKey = debugLineBoxCache.keys.first; // fontSize 2 - await pumpSpinnerWithFontSize(2); + // Re-querying an existing cached style (e.g. fontSize 2) promotes it + // and does not expand cache length or evict any entry. + final secondKey = debugLineBoxCache.keys.first; // fontSize 2 + await pumpSpinnerWithFontSize(2); - check(debugLineBoxCache.length).equals(16); - check(debugLineBoxCache.containsKey(secondKey)).isTrue(); - // secondKey was re-inserted at the end of LRU cache, so it is no - // longer the first key. - check(debugLineBoxCache.keys.first).not((it) => it.equals(secondKey)); + check(debugLineBoxCache.length).equals(16); + check(debugLineBoxCache.containsKey(secondKey)).isTrue(); + // secondKey was re-inserted at the end of LRU cache, so it is no + // longer the first key. + check(debugLineBoxCache.keys.first).not((it) => it.equals(secondKey)); - debugLineBoxCache.clear(); - }, - ); + debugLineBoxCache.clear(); + }); }); } From c3c54b95c187447b1d2efdb74cd364c8bb88967c Mon Sep 17 00:00:00 2001 From: Mehmet Esen Date: Sun, 23 Aug 2026 16:53:59 +0300 Subject: [PATCH 3/3] test: tighten line-box cache oracle and type debugLineBoxCache precisely Type the @visibleForTesting getter as Map<(TextStyle, TextDirection, TextScaler), double> instead of Map, removing the covariance trap and letting the test assert which entry is oldest via the record's fontSize. Move the test into its own 'AsyncButtonSpinner line box cache' group, reset the global cache in addTearDown so a failing assertion cannot leak entries into later tests, replace the tautological containsKey check with fontSize assertions, and assert promotion as keys.last == secondKey. Co-Authored-By: Claude Fable 5 --- lib/src/material_async_button_theme.dart | 6 +- test/material_async_button_theme_test.dart | 86 +++++++++++----------- 2 files changed, 45 insertions(+), 47 deletions(-) diff --git a/lib/src/material_async_button_theme.dart b/lib/src/material_async_button_theme.dart index 5cb7fa7..7c95596 100644 --- a/lib/src/material_async_button_theme.dart +++ b/lib/src/material_async_button_theme.dart @@ -94,9 +94,11 @@ class AsyncButtonTheme extends ThemeExtension { typedef _LineBoxKey = (TextStyle, TextDirection, TextScaler); final _lineBoxCache = <_LineBoxKey, double>{}; -/// Exposes the line box cache for testing cache limits and eviction. +/// 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 get debugLineBoxCache => _lineBoxCache; +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 diff --git a/test/material_async_button_theme_test.dart b/test/material_async_button_theme_test.dart index 13e5d39..7e31e94 100644 --- a/test/material_async_button_theme_test.dart +++ b/test/material_async_button_theme_test.dart @@ -141,53 +141,49 @@ void main() { check(AsyncButtonTheme.empty.loadingBuilder).isNull(); check(AsyncButtonTheme.empty.transitionBuilder).isNull(); }); + }); - testWidgets('AsyncButtonSpinner line box cache evicts oldest entry when ' - 'capacity exceeds 16', (tester) async { - debugLineBoxCache.clear(); - - Future pumpSpinnerWithFontSize(double fontSize) async { - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DefaultTextStyle( - style: TextStyle(fontSize: fontSize), - child: const AsyncButtonSpinner(), + 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(), + ), ), ), - ), - ); - } - - // Pump 16 distinct text styles to fill cache to its max capacity of 16. - for (var i = 1; i <= 16; i++) { - await pumpSpinnerWithFontSize(i.toDouble()); - } - - check(debugLineBoxCache.length).equals(16); - - // Identify the key for fontSize 1 (the first inserted entry). - final firstKey = debugLineBoxCache.keys.first; - check(debugLineBoxCache.containsKey(firstKey)).isTrue(); - - // Pump 17th distinct text style. This should evict oldest entry. - await pumpSpinnerWithFontSize(17); - - check(debugLineBoxCache.length).equals(16); - check(debugLineBoxCache.containsKey(firstKey)).isFalse(); - - // Re-querying an existing cached style (e.g. fontSize 2) promotes it - // and does not expand cache length or evict any entry. - final secondKey = debugLineBoxCache.keys.first; // fontSize 2 - await pumpSpinnerWithFontSize(2); - - check(debugLineBoxCache.length).equals(16); - check(debugLineBoxCache.containsKey(secondKey)).isTrue(); - // secondKey was re-inserted at the end of LRU cache, so it is no - // longer the first key. - check(debugLineBoxCache.keys.first).not((it) => it.equals(secondKey)); - - debugLineBoxCache.clear(); - }); + ); + } + + // 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); + }, + ); }); }