Skip to content

Commit 77c19f5

Browse files
authored
Test cover cupertino for memory leaks and fix Opacity Layer not disposed. (#136576)
1 parent 0f55504 commit 77c19f5

File tree

3 files changed

+49
-41
lines changed

3 files changed

+49
-41
lines changed

packages/flutter/lib/src/rendering/list_wheel_viewport.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -824,9 +824,10 @@ class RenderListWheelViewport
824824
@override
825825
void dispose() {
826826
_clipRectLayer.layer = null;
827+
_childOpacityLayerHandler.layer = null;
827828
super.dispose();
828829
}
829-
830+
final LayerHandle<OpacityLayer> _childOpacityLayerHandler = LayerHandle<OpacityLayer>();
830831
/// Paints all children visible in the current viewport.
831832
void _paintVisibleChildren(PaintingContext context, Offset offset) {
832833
// The magnifier cannot be turned off if the opacity is less than 1.0.
@@ -837,7 +838,7 @@ class RenderListWheelViewport
837838

838839
// In order to reduce the number of opacity layers, we first paint all
839840
// partially opaque children, then finally paint the fully opaque children.
840-
context.pushOpacity(offset, (overAndUnderCenterOpacity * 255).round(), (PaintingContext context, Offset offset) {
841+
_childOpacityLayerHandler.layer = context.pushOpacity(offset, (overAndUnderCenterOpacity * 255).round(), (PaintingContext context, Offset offset) {
841842
_paintAllChildren(context, offset, center: false);
842843
});
843844
_paintAllChildren(context, offset, center: true);

packages/flutter/test/cupertino/picker_test.dart

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
88
import 'package:flutter/rendering.dart';
99
import 'package:flutter/services.dart';
1010
import 'package:flutter_test/flutter_test.dart';
11+
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
1112

1213
import '../rendering/rendering_tester.dart';
1314

@@ -18,7 +19,7 @@ class SpyFixedExtentScrollController extends FixedExtentScrollController {
1819
}
1920

2021
void main() {
21-
testWidgets('Picker respects theme styling', (WidgetTester tester) async {
22+
testWidgetsWithLeakTracking('Picker respects theme styling', (WidgetTester tester) async {
2223
await tester.pumpWidget(
2324
CupertinoApp(
2425
home: Align(
@@ -57,7 +58,7 @@ void main() {
5758

5859
group('layout', () {
5960
// Regression test for https://github.com/flutter/flutter/issues/22999
60-
testWidgets('CupertinoPicker.builder test', (WidgetTester tester) async {
61+
testWidgetsWithLeakTracking('CupertinoPicker.builder test', (WidgetTester tester) async {
6162
Widget buildFrame(int childCount) {
6263
return Directionality(
6364
textDirection: TextDirection.ltr,
@@ -80,9 +81,9 @@ void main() {
8081
expect(tester.renderObject(find.text('1')).attached, true);
8182
});
8283

83-
testWidgets('selected item is in the middle', (WidgetTester tester) async {
84+
testWidgetsWithLeakTracking('selected item is in the middle', (WidgetTester tester) async {
8485
final FixedExtentScrollController controller = FixedExtentScrollController(initialItem: 1);
85-
86+
addTearDown(controller.dispose);
8687
await tester.pumpWidget(
8788
Directionality(
8889
textDirection: TextDirection.ltr,
@@ -127,7 +128,7 @@ void main() {
127128
});
128129
});
129130

130-
testWidgets('picker dark mode', (WidgetTester tester) async {
131+
testWidgetsWithLeakTracking('picker dark mode', (WidgetTester tester) async {
131132
await tester.pumpWidget(
132133
CupertinoApp(
133134
theme: const CupertinoThemeData(brightness: Brightness.light),
@@ -177,9 +178,9 @@ void main() {
177178

178179
expect(find.byType(CupertinoPicker), paints..rrect(color: const Color.fromARGB(61,118, 118, 128)));
179180
expect(find.byType(CupertinoPicker), paints..rect(color: const Color(0xFF654321)));
180-
});
181+
},leakTrackingTestConfig: LeakTrackingTestConfig.debugNotDisposed());
181182

182-
testWidgets('picker selectionOverlay', (WidgetTester tester) async {
183+
testWidgetsWithLeakTracking('picker selectionOverlay', (WidgetTester tester) async {
183184
await tester.pumpWidget(
184185
CupertinoApp(
185186
theme: const CupertinoThemeData(brightness: Brightness.light),
@@ -202,7 +203,7 @@ void main() {
202203
expect(find.byType(CupertinoPicker), paints..rrect(color: const Color(0x12345678)));
203204
});
204205

205-
testWidgets('CupertinoPicker.selectionOverlay is nullable', (WidgetTester tester) async {
206+
testWidgetsWithLeakTracking('CupertinoPicker.selectionOverlay is nullable', (WidgetTester tester) async {
206207
await tester.pumpWidget(
207208
CupertinoApp(
208209
theme: const CupertinoThemeData(brightness: Brightness.light),
@@ -226,7 +227,7 @@ void main() {
226227
});
227228

228229
group('scroll', () {
229-
testWidgets(
230+
testWidgetsWithLeakTracking(
230231
'scrolling calls onSelectedItemChanged and triggers haptic feedback',
231232
(WidgetTester tester) async {
232233
final List<int> selectedItems = <int>[];
@@ -280,7 +281,7 @@ void main() {
280281
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
281282
);
282283

283-
testWidgets(
284+
testWidgetsWithLeakTracking(
284285
'do not trigger haptic effects on non-iOS devices',
285286
(WidgetTester tester) async {
286287
final List<int> selectedItems = <int>[];
@@ -317,8 +318,9 @@ void main() {
317318
variant: TargetPlatformVariant(TargetPlatform.values.where((TargetPlatform platform) => platform != TargetPlatform.iOS).toSet()),
318319
);
319320

320-
testWidgets('a drag in between items settles back', (WidgetTester tester) async {
321+
testWidgetsWithLeakTracking('a drag in between items settles back', (WidgetTester tester) async {
321322
final FixedExtentScrollController controller = FixedExtentScrollController(initialItem: 10);
323+
addTearDown(controller.dispose);
322324
final List<int> selectedItems = <int>[];
323325

324326
await tester.pumpWidget(
@@ -371,9 +373,10 @@ void main() {
371373
expect(selectedItems, <int>[9]);
372374
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
373375

374-
testWidgets('a big fling that overscrolls springs back', (WidgetTester tester) async {
376+
testWidgetsWithLeakTracking('a big fling that overscrolls springs back', (WidgetTester tester) async {
375377
final FixedExtentScrollController controller =
376378
FixedExtentScrollController(initialItem: 10);
379+
addTearDown(controller.dispose);
377380
final List<int> selectedItems = <int>[];
378381

379382
await tester.pumpWidget(
@@ -433,7 +436,7 @@ void main() {
433436
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
434437
});
435438

436-
testWidgets('Picker adapts to MaterialApp dark mode', (WidgetTester tester) async {
439+
testWidgetsWithLeakTracking('Picker adapts to MaterialApp dark mode', (WidgetTester tester) async {
437440
Widget buildCupertinoPicker(Brightness brightness) {
438441
return MaterialApp(
439442
theme: ThemeData(brightness: brightness),
@@ -474,7 +477,7 @@ void main() {
474477
});
475478

476479
group('CupertinoPickerDefaultSelectionOverlay', () {
477-
testWidgets('should be using directional decoration', (WidgetTester tester) async {
480+
testWidgetsWithLeakTracking('should be using directional decoration', (WidgetTester tester) async {
478481
await tester.pumpWidget(
479482
CupertinoApp(
480483
theme: const CupertinoThemeData(brightness: Brightness.light),
@@ -497,8 +500,9 @@ void main() {
497500
});
498501
});
499502

500-
testWidgets('Scroll controller is detached upon dispose', (WidgetTester tester) async {
503+
testWidgetsWithLeakTracking('Scroll controller is detached upon dispose', (WidgetTester tester) async {
501504
final SpyFixedExtentScrollController controller = SpyFixedExtentScrollController();
505+
addTearDown(controller.dispose);
502506
expect(controller.hasListeners, false);
503507
expect(controller.positions.length, 0);
504508

@@ -528,7 +532,8 @@ void main() {
528532
expect(controller.positions.length, 0);
529533
});
530534

531-
testWidgets('Registers taps and does not crash with certain diameterRatio', (WidgetTester tester) async {
535+
testWidgetsWithLeakTracking(
536+
'Registers taps and does not crash with certain diameterRatio', (WidgetTester tester) async {
532537
// Regression test for https://github.com/flutter/flutter/issues/126491
533538

534539
final List<int> children = List<int>.generate(100, (int index) => index);

packages/flutter/test/cupertino/text_form_field_row_test.dart

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
import 'package:flutter/cupertino.dart';
66
import 'package:flutter/rendering.dart';
77
import 'package:flutter_test/flutter_test.dart';
8+
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
89

910
void main() {
10-
testWidgets('Passes textAlign to underlying CupertinoTextField', (WidgetTester tester) async {
11+
testWidgetsWithLeakTracking('Passes textAlign to underlying CupertinoTextField', (WidgetTester tester) async {
1112
const TextAlign alignment = TextAlign.center;
1213

1314
await tester.pumpWidget(
@@ -27,7 +28,7 @@ void main() {
2728
expect(textFieldWidget.textAlign, alignment);
2829
});
2930

30-
testWidgets('Passes scrollPhysics to underlying TextField', (WidgetTester tester) async {
31+
testWidgetsWithLeakTracking('Passes scrollPhysics to underlying TextField', (WidgetTester tester) async {
3132
const ScrollPhysics scrollPhysics = ScrollPhysics();
3233

3334
await tester.pumpWidget(
@@ -47,7 +48,7 @@ void main() {
4748
expect(textFieldWidget.scrollPhysics, scrollPhysics);
4849
});
4950

50-
testWidgets('Passes textAlignVertical to underlying CupertinoTextField', (WidgetTester tester) async {
51+
testWidgetsWithLeakTracking('Passes textAlignVertical to underlying CupertinoTextField', (WidgetTester tester) async {
5152
const TextAlignVertical textAlignVertical = TextAlignVertical.bottom;
5253

5354
await tester.pumpWidget(
@@ -67,7 +68,7 @@ void main() {
6768
expect(textFieldWidget.textAlignVertical, textAlignVertical);
6869
});
6970

70-
testWidgets('Passes textInputAction to underlying CupertinoTextField', (WidgetTester tester) async {
71+
testWidgetsWithLeakTracking('Passes textInputAction to underlying CupertinoTextField', (WidgetTester tester) async {
7172
await tester.pumpWidget(
7273
CupertinoApp(
7374
home: Center(
@@ -85,7 +86,7 @@ void main() {
8586
expect(textFieldWidget.textInputAction, TextInputAction.next);
8687
});
8788

88-
testWidgets('Passes onEditingComplete to underlying CupertinoTextField', (WidgetTester tester) async {
89+
testWidgetsWithLeakTracking('Passes onEditingComplete to underlying CupertinoTextField', (WidgetTester tester) async {
8990
void onEditingComplete() {}
9091

9192
await tester.pumpWidget(
@@ -105,7 +106,7 @@ void main() {
105106
expect(textFieldWidget.onEditingComplete, onEditingComplete);
106107
});
107108

108-
testWidgets('Passes cursor attributes to underlying CupertinoTextField', (WidgetTester tester) async {
109+
testWidgetsWithLeakTracking('Passes cursor attributes to underlying CupertinoTextField', (WidgetTester tester) async {
109110
const double cursorWidth = 3.14;
110111
const double cursorHeight = 6.28;
111112
const Radius cursorRadius = Radius.circular(2);
@@ -133,7 +134,7 @@ void main() {
133134
expect(textFieldWidget.cursorColor, cursorColor);
134135
});
135136

136-
testWidgets('onFieldSubmit callbacks are called', (WidgetTester tester) async {
137+
testWidgetsWithLeakTracking('onFieldSubmit callbacks are called', (WidgetTester tester) async {
137138
bool called = false;
138139

139140
await tester.pumpWidget(
@@ -154,7 +155,7 @@ void main() {
154155
expect(called, true);
155156
});
156157

157-
testWidgets('onChanged callbacks are called', (WidgetTester tester) async {
158+
testWidgetsWithLeakTracking('onChanged callbacks are called', (WidgetTester tester) async {
158159
late String value;
159160

160161
await tester.pumpWidget(
@@ -174,7 +175,7 @@ void main() {
174175
expect(value, 'Soup');
175176
});
176177

177-
testWidgets('autovalidateMode is passed to super', (WidgetTester tester) async {
178+
testWidgetsWithLeakTracking('autovalidateMode is passed to super', (WidgetTester tester) async {
178179
int validateCalled = 0;
179180

180181
await tester.pumpWidget(
@@ -197,7 +198,7 @@ void main() {
197198
expect(validateCalled, 2);
198199
});
199200

200-
testWidgets('validate is called if widget is enabled', (WidgetTester tester) async {
201+
testWidgetsWithLeakTracking('validate is called if widget is enabled', (WidgetTester tester) async {
201202
int validateCalled = 0;
202203

203204
await tester.pumpWidget(
@@ -221,7 +222,7 @@ void main() {
221222
expect(validateCalled, 2);
222223
});
223224

224-
testWidgets('readonly text form field will hide cursor by default', (WidgetTester tester) async {
225+
testWidgetsWithLeakTracking('readonly text form field will hide cursor by default', (WidgetTester tester) async {
225226
await tester.pumpWidget(
226227
CupertinoApp(
227228
home: Center(
@@ -262,7 +263,7 @@ void main() {
262263
expect(renderEditable, paintsExactlyCountTimes(#drawRect, 0));
263264
}, skip: isBrowser); // [intended] We do not use Flutter-rendered context menu on the Web.
264265

265-
testWidgets('onTap is called upon tap', (WidgetTester tester) async {
266+
testWidgetsWithLeakTracking('onTap is called upon tap', (WidgetTester tester) async {
266267
int tapCount = 0;
267268
await tester.pumpWidget(
268269
CupertinoApp(
@@ -288,7 +289,7 @@ void main() {
288289
});
289290

290291
// Regression test for https://github.com/flutter/flutter/issues/54472.
291-
testWidgets('reset resets the text fields value to the initialValue', (WidgetTester tester) async {
292+
testWidgetsWithLeakTracking('reset resets the text fields value to the initialValue', (WidgetTester tester) async {
292293
await tester.pumpWidget(CupertinoApp(
293294
home: Center(
294295
child: CupertinoTextFormFieldRow(
@@ -307,7 +308,7 @@ void main() {
307308
});
308309

309310
// Regression test for https://github.com/flutter/flutter/issues/54472.
310-
testWidgets('didChange changes text fields value', (WidgetTester tester) async {
311+
testWidgetsWithLeakTracking('didChange changes text fields value', (WidgetTester tester) async {
311312
await tester.pumpWidget(CupertinoApp(
312313
home: Center(
313314
child: CupertinoTextFormFieldRow(
@@ -326,7 +327,7 @@ void main() {
326327
expect(find.text('changedValue'), findsOneWidget);
327328
});
328329

329-
testWidgets('onChanged callbacks value and FormFieldState.value are sync', (WidgetTester tester) async {
330+
testWidgetsWithLeakTracking('onChanged callbacks value and FormFieldState.value are sync', (WidgetTester tester) async {
330331
bool called = false;
331332

332333
late FormFieldState<String> state;
@@ -352,7 +353,7 @@ void main() {
352353
expect(called, true);
353354
});
354355

355-
testWidgets('autofillHints is passed to super', (WidgetTester tester) async {
356+
testWidgetsWithLeakTracking('autofillHints is passed to super', (WidgetTester tester) async {
356357
await tester.pumpWidget(
357358
CupertinoApp(
358359
home: Center(
@@ -368,7 +369,7 @@ void main() {
368369
expect(widget.autofillHints, equals(const <String>[AutofillHints.countryName]));
369370
});
370371

371-
testWidgets('autovalidateMode is passed to super', (WidgetTester tester) async {
372+
testWidgetsWithLeakTracking('autovalidateMode is passed to super', (WidgetTester tester) async {
372373
int validateCalled = 0;
373374

374375
await tester.pumpWidget(
@@ -391,7 +392,7 @@ void main() {
391392
expect(validateCalled, 1);
392393
});
393394

394-
testWidgets('AutovalidateMode.always mode shows error from the start', (WidgetTester tester) async {
395+
testWidgetsWithLeakTracking('AutovalidateMode.always mode shows error from the start', (WidgetTester tester) async {
395396
await tester.pumpWidget(
396397
CupertinoApp(
397398
home: Center(
@@ -411,9 +412,9 @@ void main() {
411412
expect(errorText.data, 'Error');
412413
});
413414

414-
testWidgets('Shows error text upon invalid input', (WidgetTester tester) async {
415+
testWidgetsWithLeakTracking('Shows error text upon invalid input', (WidgetTester tester) async {
415416
final TextEditingController controller = TextEditingController(text: '');
416-
417+
addTearDown(controller.dispose);
417418
await tester.pumpWidget(
418419
CupertinoApp(
419420
home: Center(
@@ -439,7 +440,7 @@ void main() {
439440
expect(errorText.data, 'Error');
440441
});
441442

442-
testWidgets('Shows prefix', (WidgetTester tester) async {
443+
testWidgetsWithLeakTracking('Shows prefix', (WidgetTester tester) async {
443444
await tester.pumpWidget(
444445
CupertinoApp(
445446
home: Center(
@@ -457,7 +458,7 @@ void main() {
457458
expect(errorText.data, 'Enter Value');
458459
});
459460

460-
testWidgets('Passes textDirection to underlying CupertinoTextField', (WidgetTester tester) async {
461+
testWidgetsWithLeakTracking('Passes textDirection to underlying CupertinoTextField', (WidgetTester tester) async {
461462
await tester.pumpWidget(
462463
CupertinoApp(
463464
home: Center(
@@ -491,7 +492,8 @@ void main() {
491492
expect(rtlTextFieldWidget.textDirection, TextDirection.rtl);
492493
});
493494

494-
testWidgets('CupertinoTextFormFieldRow onChanged is called when the form is reset', (WidgetTester tester) async {
495+
testWidgetsWithLeakTracking(
496+
'CupertinoTextFormFieldRow onChanged is called when the form is reset', (WidgetTester tester) async {
495497
// Regression test for https://github.com/flutter/flutter/issues/123009.
496498
final GlobalKey<FormFieldState<String>> stateKey = GlobalKey<FormFieldState<String>>();
497499
final GlobalKey<FormState> formKey = GlobalKey<FormState>();

0 commit comments

Comments
 (0)