Unverified Commit 9b230d23 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub
parent 65dfb555
...@@ -297,7 +297,7 @@ class _ZoomEnterTransition extends StatefulWidget { ...@@ -297,7 +297,7 @@ class _ZoomEnterTransition extends StatefulWidget {
State<_ZoomEnterTransition> createState() => _ZoomEnterTransitionState(); State<_ZoomEnterTransition> createState() => _ZoomEnterTransitionState();
} }
class _ZoomEnterTransitionState extends State<_ZoomEnterTransition> with _ZoomTransitionBase { class _ZoomEnterTransitionState extends State<_ZoomEnterTransition> with _ZoomTransitionBase<_ZoomEnterTransition> {
// See SnapshotWidget doc comment, this is disabled on web because the HTML backend doesn't // See SnapshotWidget doc comment, this is disabled on web because the HTML backend doesn't
// support this functionality and the canvaskit backend uses a single thread for UI and raster // support this functionality and the canvaskit backend uses a single thread for UI and raster
// work which diminishes the impact of this performance improvement. // work which diminishes the impact of this performance improvement.
...@@ -406,7 +406,7 @@ class _ZoomExitTransition extends StatefulWidget { ...@@ -406,7 +406,7 @@ class _ZoomExitTransition extends StatefulWidget {
State<_ZoomExitTransition> createState() => _ZoomExitTransitionState(); State<_ZoomExitTransition> createState() => _ZoomExitTransitionState();
} }
class _ZoomExitTransitionState extends State<_ZoomExitTransition> with _ZoomTransitionBase { class _ZoomExitTransitionState extends State<_ZoomExitTransition> with _ZoomTransitionBase<_ZoomExitTransition> {
late _ZoomExitTransitionPainter delegate; late _ZoomExitTransitionPainter delegate;
// See SnapshotWidget doc comment, this is disabled on web because the HTML backend doesn't // See SnapshotWidget doc comment, this is disabled on web because the HTML backend doesn't
...@@ -830,7 +830,7 @@ void _updateScaledTransform(Matrix4 transform, double scale, Size size) { ...@@ -830,7 +830,7 @@ void _updateScaledTransform(Matrix4 transform, double scale, Size size) {
transform.translate(-dx, -dy); transform.translate(-dx, -dy);
} }
mixin _ZoomTransitionBase { mixin _ZoomTransitionBase<S extends StatefulWidget> on State<S> {
bool get useSnapshot; bool get useSnapshot;
// Don't rasterize if: // Don't rasterize if:
...@@ -863,6 +863,12 @@ mixin _ZoomTransitionBase { ...@@ -863,6 +863,12 @@ mixin _ZoomTransitionBase {
controller.allowSnapshotting = useSnapshot; controller.allowSnapshotting = useSnapshot;
} }
} }
@override
void dispose() {
controller.dispose();
super.dispose();
}
} }
class _ZoomEnterTransitionPainter extends SnapshotPainter { class _ZoomEnterTransitionPainter extends SnapshotPainter {
......
...@@ -885,6 +885,7 @@ class _DragAvatar<T extends Object> extends Drag { ...@@ -885,6 +885,7 @@ class _DragAvatar<T extends Object> extends Drag {
_leaveAllEntered(); _leaveAllEntered();
_activeTarget = null; _activeTarget = null;
_entry!.remove(); _entry!.remove();
_entry!.dispose();
_entry = null; _entry = null;
// TODO(ianh): consider passing _entry as well so the client can perform an animation. // TODO(ianh): consider passing _entry as well so the client can perform an animation.
onDragEnd?.call(velocity ?? Velocity.zero, _lastOffset!, wasAccepted); onDragEnd?.call(velocity ?? Velocity.zero, _lastOffset!, wasAccepted);
......
...@@ -454,6 +454,7 @@ abstract class Route<T> { ...@@ -454,6 +454,7 @@ abstract class Route<T> {
@protected @protected
void dispose() { void dispose() {
_navigator = null; _navigator = null;
_restorationScopeId.dispose();
} }
/// Whether this route is the top-most route on the navigator. /// Whether this route is the top-most route on the navigator.
...@@ -3606,6 +3607,9 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res ...@@ -3606,6 +3607,9 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
for (final _RouteEntry entry in _history) { for (final _RouteEntry entry in _history) {
entry.dispose(); entry.dispose();
} }
_rawNextPagelessRestorationScopeId.dispose();
_serializableHistory.dispose();
userGestureInProgressNotifier.dispose();
super.dispose(); super.dispose();
// don't unlock, so that the object becomes unusable // don't unlock, so that the object becomes unusable
assert(_debugLocked); assert(_debugLocked);
......
...@@ -64,13 +64,13 @@ class TickerMode extends StatefulWidget { ...@@ -64,13 +64,13 @@ class TickerMode extends StatefulWidget {
return widget?.enabled ?? true; return widget?.enabled ?? true;
} }
/// Obtains a [ValueNotifier] from the [TickerMode] surrounding the `context`, /// Obtains a [ValueListenable] from the [TickerMode] surrounding the `context`,
/// which indicates whether tickers are enabled in the given subtree. /// which indicates whether tickers are enabled in the given subtree.
/// ///
/// When that [TickerMode] enabled or disabled tickers, the notifier notifies /// When that [TickerMode] enabled or disabled tickers, the listenable notifies
/// its listeners. /// its listeners.
/// ///
/// While the [ValueNotifier] is stable for the lifetime of the surrounding /// While the [ValueListenable] is stable for the lifetime of the surrounding
/// [TickerMode], calling this method does not establish a dependency between /// [TickerMode], calling this method does not establish a dependency between
/// the `context` and the [TickerMode] and the widget owning the `context` /// the `context` and the [TickerMode] and the widget owning the `context`
/// does not rebuild when the ticker mode changes from true to false or vice /// does not rebuild when the ticker mode changes from true to false or vice
...@@ -79,7 +79,7 @@ class TickerMode extends StatefulWidget { ...@@ -79,7 +79,7 @@ class TickerMode extends StatefulWidget {
/// [Ticker]. Since no dependency is established, the widget owning the /// [Ticker]. Since no dependency is established, the widget owning the
/// `context` is also not informed when it is moved to a new location in the /// `context` is also not informed when it is moved to a new location in the
/// tree where it may have a different [TickerMode] ancestor. When this /// tree where it may have a different [TickerMode] ancestor. When this
/// happens, the widget must manually unsubscribe from the old notifier, /// happens, the widget must manually unsubscribe from the old listenable,
/// obtain a new one from the new ancestor [TickerMode] by calling this method /// obtain a new one from the new ancestor [TickerMode] by calling this method
/// again, and re-subscribe to it. [StatefulWidget]s can, for example, do this /// again, and re-subscribe to it. [StatefulWidget]s can, for example, do this
/// in [State.activate], which is called after the widget has been moved to /// in [State.activate], which is called after the widget has been moved to
...@@ -93,10 +93,10 @@ class TickerMode extends StatefulWidget { ...@@ -93,10 +93,10 @@ class TickerMode extends StatefulWidget {
/// potential unnecessary rebuilds. /// potential unnecessary rebuilds.
/// ///
/// In the absence of a [TickerMode] widget, this function returns a /// In the absence of a [TickerMode] widget, this function returns a
/// [ValueNotifier], whose [ValueNotifier.value] is always true. /// [ValueListenable], whose [ValueListenable.value] is always true.
static ValueNotifier<bool> getNotifier(BuildContext context) { static ValueListenable<bool> getNotifier(BuildContext context) {
final _EffectiveTickerMode? widget = context.getInheritedWidgetOfExactType<_EffectiveTickerMode>(); final _EffectiveTickerMode? widget = context.getInheritedWidgetOfExactType<_EffectiveTickerMode>();
return widget?.notifier ?? ValueNotifier<bool>(true); return widget?.notifier ?? const _ConstantValueListenable<bool>(true);
} }
@override @override
...@@ -228,7 +228,7 @@ mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> imple ...@@ -228,7 +228,7 @@ mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> imple
super.dispose(); super.dispose();
} }
ValueNotifier<bool>? _tickerModeNotifier; ValueListenable<bool>? _tickerModeNotifier;
@override @override
void activate() { void activate() {
...@@ -245,7 +245,7 @@ mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> imple ...@@ -245,7 +245,7 @@ mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> imple
} }
void _updateTickerModeNotifier() { void _updateTickerModeNotifier() {
final ValueNotifier<bool> newNotifier = TickerMode.getNotifier(context); final ValueListenable<bool> newNotifier = TickerMode.getNotifier(context);
if (newNotifier == _tickerModeNotifier) { if (newNotifier == _tickerModeNotifier) {
return; return;
} }
...@@ -307,7 +307,7 @@ mixin TickerProviderStateMixin<T extends StatefulWidget> on State<T> implements ...@@ -307,7 +307,7 @@ mixin TickerProviderStateMixin<T extends StatefulWidget> on State<T> implements
_tickers!.remove(ticker); _tickers!.remove(ticker);
} }
ValueNotifier<bool>? _tickerModeNotifier; ValueListenable<bool>? _tickerModeNotifier;
@override @override
void activate() { void activate() {
...@@ -327,7 +327,7 @@ mixin TickerProviderStateMixin<T extends StatefulWidget> on State<T> implements ...@@ -327,7 +327,7 @@ mixin TickerProviderStateMixin<T extends StatefulWidget> on State<T> implements
} }
void _updateTickerModeNotifier() { void _updateTickerModeNotifier() {
final ValueNotifier<bool> newNotifier = TickerMode.getNotifier(context); final ValueListenable<bool> newNotifier = TickerMode.getNotifier(context);
if (newNotifier == _tickerModeNotifier) { if (newNotifier == _tickerModeNotifier) {
return; return;
} }
...@@ -395,3 +395,22 @@ class _WidgetTicker extends Ticker { ...@@ -395,3 +395,22 @@ class _WidgetTicker extends Ticker {
super.dispose(); super.dispose();
} }
} }
class _ConstantValueListenable<T> implements ValueListenable<T> {
const _ConstantValueListenable(this.value);
@override
void addListener(VoidCallback listener) {
// Intentionally left empty: Value cannot change, so we never have to
// notify registered listeners.
}
@override
void removeListener(VoidCallback listener) {
// Intentionally left empty: Value cannot change, so we never have to
// notify registered listeners.
}
@override
final T value;
}
...@@ -7,18 +7,20 @@ ...@@ -7,18 +7,20 @@
@Tags(<String>['reduced-test-set']) @Tags(<String>['reduced-test-set'])
library; library;
import 'dart:ui' as ui;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../foundation/leak_tracking.dart';
void main() { void main() {
/* /*
* Here lies tests for packages/flutter_test/lib/src/animation_sheet.dart * Here lies tests for packages/flutter_test/lib/src/animation_sheet.dart
* because [matchesGoldenFile] does not use Skia Gold in its native package. * because [matchesGoldenFile] does not use Skia Gold in its native package.
*/ */
// TODO(polina-c): fix ValueNotifier not disposed and switch to testWidgetsWithLeakTracking. testWidgetsWithLeakTracking('correctly records frames using display', (WidgetTester tester) async {
// https://github.com/flutter/devtools/issues/3951
testWidgets('correctly records frames using display', (WidgetTester tester) async {
final AnimationSheetBuilder builder = AnimationSheetBuilder(frameSize: _DecuplePixels.size); final AnimationSheetBuilder builder = AnimationSheetBuilder(frameSize: _DecuplePixels.size);
await tester.pumpFrames( await tester.pumpFrames(
...@@ -54,9 +56,7 @@ void main() { ...@@ -54,9 +56,7 @@ void main() {
await expectLater(find.byWidget(display), matchesGoldenFile('test.animation_sheet_builder.records.png')); await expectLater(find.byWidget(display), matchesGoldenFile('test.animation_sheet_builder.records.png'));
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001 }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001
// TODO(polina-c): fix ValueNotifier not disposed and switch to testWidgetsWithLeakTracking. testWidgetsWithLeakTracking('correctly wraps a row', (WidgetTester tester) async {
// https://github.com/flutter/devtools/issues/3951
testWidgets('correctly wraps a row', (WidgetTester tester) async {
final AnimationSheetBuilder builder = AnimationSheetBuilder(frameSize: _DecuplePixels.size); final AnimationSheetBuilder builder = AnimationSheetBuilder(frameSize: _DecuplePixels.size);
const Duration duration = Duration(seconds: 2); const Duration duration = Duration(seconds: 2);
...@@ -74,9 +74,7 @@ void main() { ...@@ -74,9 +74,7 @@ void main() {
await expectLater(find.byWidget(display), matchesGoldenFile('test.animation_sheet_builder.wraps.png')); await expectLater(find.byWidget(display), matchesGoldenFile('test.animation_sheet_builder.wraps.png'));
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001 }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001
// TODO(polina-c): fix Picture and Image not disposed and and switch to testWidgetsWithLeakTracking. testWidgetsWithLeakTracking('correctly records frames using collate', (WidgetTester tester) async {
// https://github.com/flutter/devtools/issues/3951
testWidgets('correctly records frames using collate', (WidgetTester tester) async {
final AnimationSheetBuilder builder = AnimationSheetBuilder(frameSize: _DecuplePixels.size); final AnimationSheetBuilder builder = AnimationSheetBuilder(frameSize: _DecuplePixels.size);
await tester.pumpFrames( await tester.pumpFrames(
...@@ -104,15 +102,16 @@ void main() { ...@@ -104,15 +102,16 @@ void main() {
const Duration(milliseconds: 100), const Duration(milliseconds: 100),
); );
final ui.Image image = await builder.collate(5);
await expectLater( await expectLater(
builder.collate(5), image,
matchesGoldenFile('test.animation_sheet_builder.collate.png'), matchesGoldenFile('test.animation_sheet_builder.collate.png'),
); );
image.dispose();
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001 }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001
// TODO(polina-c): fix Picture and Image not disposed and switch to testWidgetsWithLeakTracking. testWidgetsWithLeakTracking('use allLayers to record out-of-subtree contents', (WidgetTester tester) async {
// https://github.com/flutter/devtools/issues/3951
testWidgets('use allLayers to record out-of-subtree contents', (WidgetTester tester) async {
final AnimationSheetBuilder builder = AnimationSheetBuilder( final AnimationSheetBuilder builder = AnimationSheetBuilder(
frameSize: const Size(8, 2), frameSize: const Size(8, 2),
allLayers: true, allLayers: true,
...@@ -137,12 +136,14 @@ void main() { ...@@ -137,12 +136,14 @@ void main() {
const Duration(milliseconds: 100), const Duration(milliseconds: 100),
); );
final ui.Image image = await builder.collate(5);
await expectLater( await expectLater(
builder.collate(5), image,
matchesGoldenFile('test.animation_sheet_builder.out_of_tree.png'), matchesGoldenFile('test.animation_sheet_builder.out_of_tree.png'),
); );
image.dispose();
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001 }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001
} }
// An animation of a yellow pixel moving from left to right, in a container of // An animation of a yellow pixel moving from left to right, in a container of
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
@Tags(<String>['reduced-test-set']) @Tags(<String>['reduced-test-set'])
library; library;
import 'dart:ui' as ui;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
...@@ -78,9 +80,7 @@ void main() { ...@@ -78,9 +80,7 @@ void main() {
// Currently skipped due to daily flake: https://github.com/flutter/flutter/issues/87588 // Currently skipped due to daily flake: https://github.com/flutter/flutter/issues/87588
}, skip: true); // Typically skip: isBrowser https://github.com/flutter/flutter/issues/42767 }, skip: true); // Typically skip: isBrowser https://github.com/flutter/flutter/issues/42767
// TODO(polina-c): fix ValueNotifier not disposed and switch to testWidgetsWithLeakTracking. testWidgetsWithLeakTracking('Should show event indicator for pointer events with setSurfaceSize', (WidgetTester tester) async {
// https://github.com/flutter/devtools/issues/3951
testWidgets('Should show event indicator for pointer events with setSurfaceSize', (WidgetTester tester) async {
final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(frameSize: const Size(200, 200), allLayers: true); final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(frameSize: const Size(200, 200), allLayers: true);
final List<Offset> taps = <Offset>[]; final List<Offset> taps = <Offset>[];
Widget target({bool recording = true}) => Container( Widget target({bool recording = true}) => Container(
...@@ -132,9 +132,12 @@ void main() { ...@@ -132,9 +132,12 @@ void main() {
await tester.pumpFrames(target(), const Duration(milliseconds: 50)); await tester.pumpFrames(target(), const Duration(milliseconds: 50));
expect(taps, isEmpty); expect(taps, isEmpty);
final ui.Image image = await animationSheet.collate(6);
await expectLater( await expectLater(
animationSheet.collate(6), image,
matchesGoldenFile('LiveBinding.press.animation.2.png'), matchesGoldenFile('LiveBinding.press.animation.2.png'),
); );
image.dispose();
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001 }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001
} }
...@@ -7,6 +7,8 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -7,6 +7,8 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker/leak_tracker.dart'; import 'package:leak_tracker/leak_tracker.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
export 'package:leak_tracker/leak_tracker.dart' show LeakTrackingTestConfig, StackTraceCollectionConfig;
/// Set of objects, that does not hold the objects from garbage collection. /// Set of objects, that does not hold the objects from garbage collection.
/// ///
/// The objects are referenced by hash codes and can duplicate with low probability. /// The objects are referenced by hash codes and can duplicate with low probability.
......
...@@ -114,9 +114,7 @@ void main() { ...@@ -114,9 +114,7 @@ void main() {
expect(result.dragUpdate, true); expect(result.dragUpdate, true);
}); });
// TODO(polina-c): fix ValueNotifier not disposed and switch to testWidgetsWithLeakTracking. testWidgetsWithLeakTracking('Scroll Views get the same ScrollConfiguration as Draggables', (WidgetTester tester) async {
// https://github.com/flutter/devtools/issues/3951
testWidgets('Scroll Views get the same ScrollConfiguration as Draggables', (WidgetTester tester) async {
tester.view.gestureSettings = const ui.GestureSettings(physicalTouchSlop: 4); tester.view.gestureSettings = const ui.GestureSettings(physicalTouchSlop: 4);
addTearDown(tester.view.reset); addTearDown(tester.view.reset);
......
...@@ -150,9 +150,7 @@ void main() { ...@@ -150,9 +150,7 @@ void main() {
expect(find.text('About flutter_tester'), findsOneWidget); expect(find.text('About flutter_tester'), findsOneWidget);
}); });
// TODO(polina-c): fix SnapshotController not disposed and switch to testWidgetsWithLeakTracking. testWidgetsWithLeakTracking('LicensePage control test', (WidgetTester tester) async {
// https://github.com/flutter/devtools/issues/3951
testWidgets('LicensePage control test', (WidgetTester tester) async {
LicenseRegistry.addLicense(() { LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[ return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['AAA'], 'BBB'), const LicenseEntryWithLineBreaks(<String>['AAA'], 'BBB'),
...@@ -203,9 +201,7 @@ void main() { ...@@ -203,9 +201,7 @@ void main() {
expect(find.text('Another license'), findsOneWidget); expect(find.text('Another license'), findsOneWidget);
}); });
// TODO(polina-c): fix SnapshotController not disposed and switch to testWidgetsWithLeakTracking. testWidgetsWithLeakTracking('LicensePage control test with all properties', (WidgetTester tester) async {
// https://github.com/flutter/devtools/issues/3951
testWidgets('LicensePage control test with all properties', (WidgetTester tester) async {
const FlutterLogo logo = FlutterLogo(); const FlutterLogo logo = FlutterLogo();
LicenseRegistry.addLicense(() { LicenseRegistry.addLicense(() {
...@@ -408,9 +404,7 @@ void main() { ...@@ -408,9 +404,7 @@ void main() {
); );
}); });
// TODO(polina-c): fix SnapshotController not disposed and switch to testWidgetsWithLeakTracking. testWidgetsWithLeakTracking('LicensePage returns early if unmounted', (WidgetTester tester) async {
// https://github.com/flutter/devtools/issues/3951
testWidgets('LicensePage returns early if unmounted', (WidgetTester tester) async {
final Completer<LicenseEntry> licenseCompleter = Completer<LicenseEntry>(); final Completer<LicenseEntry> licenseCompleter = Completer<LicenseEntry>();
LicenseRegistry.addLicense(() { LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromFuture(licenseCompleter.future); return Stream<LicenseEntry>.fromFuture(licenseCompleter.future);
...@@ -433,11 +427,9 @@ void main() { ...@@ -433,11 +427,9 @@ void main() {
final FakeLicenseEntry licenseEntry = FakeLicenseEntry(); final FakeLicenseEntry licenseEntry = FakeLicenseEntry();
licenseCompleter.complete(licenseEntry); licenseCompleter.complete(licenseEntry);
expect(licenseEntry.packagesCalled, false); expect(licenseEntry.packagesCalled, false);
}); }, leakTrackingConfig: const LeakTrackingTestConfig(notDisposedAllowList: <String>{'ValueNotifier<_OverlayEntryWidgetState?>'})); // TODO(goderbauer): Fix leak, https://github.com/flutter/flutter/issues/126100.
// TODO(polina-c): fix SnapshotController not disposed and switch to testWidgetsWithLeakTracking. testWidgetsWithLeakTracking('LicensePage returns late if unmounted', (WidgetTester tester) async {
// https://github.com/flutter/devtools/issues/3951
testWidgets('LicensePage returns late if unmounted', (WidgetTester tester) async {
final Completer<LicenseEntry> licenseCompleter = Completer<LicenseEntry>(); final Completer<LicenseEntry> licenseCompleter = Completer<LicenseEntry>();
LicenseRegistry.addLicense(() { LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromFuture(licenseCompleter.future); return Stream<LicenseEntry>.fromFuture(licenseCompleter.future);
...@@ -460,7 +452,7 @@ void main() { ...@@ -460,7 +452,7 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(licenseEntry.packagesCalled, true); expect(licenseEntry.packagesCalled, true);
}); }, leakTrackingConfig: const LeakTrackingTestConfig(notDisposedAllowList: <String>{'ValueNotifier<_OverlayEntryWidgetState?>'})); // TODO(goderbauer): Fix leak, https://github.com/flutter/flutter/issues/126100.
testWidgetsWithLeakTracking('LicensePage logic defaults to executable name for app name', (WidgetTester tester) async { testWidgetsWithLeakTracking('LicensePage logic defaults to executable name for app name', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -1075,9 +1067,7 @@ void main() { ...@@ -1075,9 +1067,7 @@ void main() {
expect(find.text('Exception: Injected failure'), findsOneWidget); expect(find.text('Exception: Injected failure'), findsOneWidget);
}); });
// TODO(polina-c): fix SnapshotController not disposed and switch to testWidgetsWithLeakTracking. testWidgetsWithLeakTracking('LicensePage master view layout position - ltr', (WidgetTester tester) async {
// https://github.com/flutter/devtools/issues/3951
testWidgets('LicensePage master view layout position - ltr', (WidgetTester tester) async {
const TextDirection textDirection = TextDirection.ltr; const TextDirection textDirection = TextDirection.ltr;
const Size defaultSize = Size(800.0, 600.0); const Size defaultSize = Size(800.0, 600.0);
const Size wideSize = Size(1200.0, 600.0); const Size wideSize = Size(1200.0, 600.0);
...@@ -1138,11 +1128,9 @@ void main() { ...@@ -1138,11 +1128,9 @@ void main() {
// Configure to show the default layout. // Configure to show the default layout.
await tester.binding.setSurfaceSize(defaultSize); await tester.binding.setSurfaceSize(defaultSize);
}); }, leakTrackingConfig: const LeakTrackingTestConfig(notDisposedAllowList: <String>{'ValueNotifier<_OverlayEntryWidgetState?>'})); // TODO(goderbauer): Fix leak, https://github.com/flutter/flutter/issues/126100.
// TODO(polina-c): fix SnapshotController not disposed and switch to testWidgetsWithLeakTracking. testWidgetsWithLeakTracking('LicensePage master view layout position - rtl', (WidgetTester tester) async {
// https://github.com/flutter/devtools/issues/3951
testWidgets('LicensePage master view layout position - rtl', (WidgetTester tester) async {
const TextDirection textDirection = TextDirection.rtl; const TextDirection textDirection = TextDirection.rtl;
const Size defaultSize = Size(800.0, 600.0); const Size defaultSize = Size(800.0, 600.0);
const Size wideSize = Size(1200.0, 600.0); const Size wideSize = Size(1200.0, 600.0);
...@@ -1203,7 +1191,7 @@ void main() { ...@@ -1203,7 +1191,7 @@ void main() {
// Configure to show the default layout. // Configure to show the default layout.
await tester.binding.setSurfaceSize(defaultSize); await tester.binding.setSurfaceSize(defaultSize);
}); }, leakTrackingConfig: const LeakTrackingTestConfig(notDisposedAllowList: <String>{'ValueNotifier<_OverlayEntryWidgetState?>'})); // TODO(goderbauer): Fix leak, https://github.com/flutter/flutter/issues/126100.
testWidgetsWithLeakTracking('License page title in lateral UI does not use AppBarTheme.foregroundColor', (WidgetTester tester) async { testWidgetsWithLeakTracking('License page title in lateral UI does not use AppBarTheme.foregroundColor', (WidgetTester tester) async {
// This is a regression test for https://github.com/flutter/flutter/issues/108991 // This is a regression test for https://github.com/flutter/flutter/issues/108991
......
...@@ -10,10 +10,10 @@ library; ...@@ -10,10 +10,10 @@ library;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../foundation/leak_tracking.dart';
void main() { void main() {
// TODO(polina-c): fix Image not disposed and switch to testWidgetsWithLeakTracking. testWidgetsWithLeakTracking('Flutter Logo golden test', (WidgetTester tester) async {
// https://github.com/flutter/devtools/issues/3951
testWidgets('Flutter Logo golden test', (WidgetTester tester) async {
final Key logo = UniqueKey(); final Key logo = UniqueKey();
await tester.pumpWidget(FlutterLogo(key: logo)); await tester.pumpWidget(FlutterLogo(key: logo));
......
...@@ -201,13 +201,16 @@ Future<ComparisonResult> compareLists(List<int>? test, List<int>? master) async ...@@ -201,13 +201,16 @@ Future<ComparisonResult> compareLists(List<int>? test, List<int>? master) async
final int height = testImage.height; final int height = testImage.height;
if (width != masterImage.width || height != masterImage.height) { if (width != masterImage.width || height != masterImage.height) {
return ComparisonResult( final ComparisonResult result = ComparisonResult(
passed: false, passed: false,
diffPercent: 1.0, diffPercent: 1.0,
error: 'Pixel test failed, image sizes do not match.\n' error: 'Pixel test failed, image sizes do not match.\n'
'Master Image: ${masterImage.width} X ${masterImage.height}\n' 'Master Image: ${masterImage.width} X ${masterImage.height}\n'
'Test Image: ${testImage.width} X ${testImage.height}', 'Test Image: ${testImage.width} X ${testImage.height}',
); );
masterImage.dispose();
testImage.dispose();
return result;
} }
int pixelDiffCount = 0; int pixelDiffCount = 0;
...@@ -264,6 +267,8 @@ Future<ComparisonResult> compareLists(List<int>? test, List<int>? master) async ...@@ -264,6 +267,8 @@ Future<ComparisonResult> compareLists(List<int>? test, List<int>? master) async
}, },
); );
} }
masterImage.dispose();
testImage.dispose();
return ComparisonResult(passed: true, diffPercent: 0.0); return ComparisonResult(passed: true, diffPercent: 0.0);
} }
......
...@@ -78,10 +78,13 @@ class MatchesGoldenFile extends AsyncMatcher { ...@@ -78,10 +78,13 @@ class MatchesGoldenFile extends AsyncMatcher {
} }
} }
Future<ui.Image?> imageFuture; Future<ui.Image?> imageFuture;
final bool disposeImage; // set to true if the matcher created and owns the image and must therefore dispose it.
if (item is Future<ui.Image?>) { if (item is Future<ui.Image?>) {
imageFuture = item; imageFuture = item;
disposeImage = false;
} else if (item is ui.Image) { } else if (item is ui.Image) {
imageFuture = Future<ui.Image>.value(item); imageFuture = Future<ui.Image>.value(item);
disposeImage = false;
} else if (item is Finder) { } else if (item is Finder) {
final Iterable<Element> elements = item.evaluate(); final Iterable<Element> elements = item.evaluate();
if (elements.isEmpty) { if (elements.isEmpty) {
...@@ -90,6 +93,7 @@ class MatchesGoldenFile extends AsyncMatcher { ...@@ -90,6 +93,7 @@ class MatchesGoldenFile extends AsyncMatcher {
return 'matched too many widgets'; return 'matched too many widgets';
} }
imageFuture = captureImage(elements.single); imageFuture = captureImage(elements.single);
disposeImage = true;
} else { } else {
throw AssertionError('must provide a Finder, Image, Future<Image>, List<int>, or Future<List<int>>'); throw AssertionError('must provide a Finder, Image, Future<Image>, List<int>, or Future<List<int>>');
} }
...@@ -100,19 +104,25 @@ class MatchesGoldenFile extends AsyncMatcher { ...@@ -100,19 +104,25 @@ class MatchesGoldenFile extends AsyncMatcher {
if (image == null) { if (image == null) {
throw AssertionError('Future<Image> completed to null'); throw AssertionError('Future<Image> completed to null');
} }
final ByteData? bytes = await image.toByteData(format: ui.ImageByteFormat.png);
if (bytes == null) {
return 'could not encode screenshot.';
}
if (autoUpdateGoldenFiles) {
await goldenFileComparator.update(testNameUri, bytes.buffer.asUint8List());
return null;
}
try { try {
final bool success = await goldenFileComparator.compare(bytes.buffer.asUint8List(), testNameUri); final ByteData? bytes = await image.toByteData(format: ui.ImageByteFormat.png);
return success ? null : 'does not match'; if (bytes == null) {
} on TestFailure catch (ex) { return 'could not encode screenshot.';
return ex.message; }
if (autoUpdateGoldenFiles) {
await goldenFileComparator.update(testNameUri, bytes.buffer.asUint8List());
return null;
}
try {
final bool success = await goldenFileComparator.compare(bytes.buffer.asUint8List(), testNameUri);
return success ? null : 'does not match';
} on TestFailure catch (ex) {
return ex.message;
}
} finally {
if (disposeImage) {
image.dispose();
}
} }
}, additionalTime: const Duration(minutes: 1)); }, additionalTime: const Duration(minutes: 1));
} }
......
...@@ -437,10 +437,13 @@ Future<ui.Image> _collateFrames(List<ui.Image> frames, Size frameSize, int cells ...@@ -437,10 +437,13 @@ Future<ui.Image> _collateFrames(List<ui.Image> frames, Size frameSize, int cells
Paint(), Paint(),
); );
} }
return recorder.endRecording().toImage( final ui.Picture picture = recorder.endRecording();
final ui.Image image = await picture.toImage(
(frameSize.width * cellsPerRow).toInt(), (frameSize.width * cellsPerRow).toInt(),
(frameSize.height * rowNum).toInt(), (frameSize.height * rowNum).toInt(),
); );
picture.dispose();
return image;
} }
// Layout children in a grid of fixed-sized cells. // Layout children in a grid of fixed-sized cells.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment