Kate Lovett 9d96df2364
Modernize framework lints (#179089)
WIP

Commits separated as follows:
- Update lints in analysis_options files
- Run `dart fix --apply`
- Clean up leftover analysis issues 
- Run `dart format .` in the right places.

Local analysis and testing passes. Checking CI now.

Part of https://github.com/flutter/flutter/issues/178827
- Adoption of flutter_lints in examples/api coming in a separate change
(cc @loic-sharma)

## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
2025-11-26 01:10:39 +00:00

830 lines
27 KiB
Dart

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file is run as part of a reduced test set in CI on Mac and Windows
// machines.
@Tags(<String>['reduced-test-set'])
library;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('The Ink widget expands when no dimensions are set', (WidgetTester tester) async {
await tester.pumpWidget(Material(child: Ink()));
expect(find.byType(Ink), findsOneWidget);
expect(tester.getSize(find.byType(Ink)), const Size(800.0, 600.0));
});
testWidgets('The Ink widget fits the specified size', (WidgetTester tester) async {
const height = 150.0;
const width = 200.0;
await tester.pumpWidget(
Material(
child: Center(
// used to constrain to child's size
child: Ink(height: height, width: width),
),
),
);
await tester.pumpAndSettle();
expect(find.byType(Ink), findsOneWidget);
expect(tester.getSize(find.byType(Ink)), const Size(width, height));
});
testWidgets('The Ink widget expands on a unspecified dimension', (WidgetTester tester) async {
const height = 150.0;
await tester.pumpWidget(
Material(
child: Center(
// used to constrain to child's size
child: Ink(height: height),
),
),
);
await tester.pumpAndSettle();
expect(find.byType(Ink), findsOneWidget);
expect(tester.getSize(find.byType(Ink)), const Size(800, height));
});
testWidgets('Material2 - InkWell widget renders an ink splash', (WidgetTester tester) async {
const splashColor = Color(0xAA0000FF);
const borderRadius = BorderRadius.all(Radius.circular(6.0));
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
home: Material(
child: Center(
child: SizedBox(
width: 200.0,
height: 60.0,
child: InkWell(borderRadius: borderRadius, splashColor: splashColor, onTap: () {}),
),
),
),
),
);
final Offset center = tester.getCenter(find.byType(InkWell));
final TestGesture gesture = await tester.startGesture(center);
await tester.pump(); // start gesture
await tester.pump(const Duration(milliseconds: 200)); // wait for splash to be well under way
final box = Material.of(tester.element(find.byType(InkWell))) as RenderBox;
expect(
box,
paints
..translate(x: 0.0, y: 0.0)
..save()
..translate(x: 300.0, y: 270.0)
..clipRRect(rrect: RRect.fromLTRBR(0.0, 0.0, 200.0, 60.0, const Radius.circular(6.0)))
..circle(x: 100.0, y: 30.0, radius: 21.0, color: splashColor)
..restore(),
);
await gesture.up();
});
testWidgets('Material3 - InkWell widget renders an ink splash', (WidgetTester tester) async {
const inkWellKey = Key('InkWell');
const splashColor = Color(0xAA0000FF);
const borderRadius = BorderRadius.all(Radius.circular(6.0));
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: SizedBox(
width: 200.0,
height: 60.0,
child: InkWell(
key: inkWellKey,
borderRadius: borderRadius,
splashColor: splashColor,
onTap: () {},
),
),
),
),
),
);
final Offset center = tester.getCenter(find.byType(InkWell));
final TestGesture gesture = await tester.startGesture(center);
await tester.pump(); // start gesture
await tester.pump(const Duration(milliseconds: 200)); // wait for splash to be well under way
final box = Material.of(tester.element(find.byType(InkWell))) as RenderBox;
if (kIsWeb) {
expect(
box,
paints
..save()
..translate(x: 0.0, y: 0.0)
..clipRect()
..save()
..translate(x: 300.0, y: 270.0)
..clipRRect(rrect: RRect.fromLTRBR(0.0, 0.0, 200.0, 60.0, const Radius.circular(6.0)))
..circle()
..restore(),
);
} else {
expect(
box,
paints
..translate(x: 0.0, y: 0.0)
..save()
..translate(x: 300.0, y: 270.0)
..clipRRect(rrect: RRect.fromLTRBR(0.0, 0.0, 200.0, 60.0, const Radius.circular(6.0)))
..rect(rect: const Rect.fromLTRB(0.0, 0.0, 200, 60))
..restore(),
);
}
// Material 3 uses the InkSparkle which uses a shader, so we can't capture
// the effect with paint methods. Use a golden test instead.
await expectLater(
find.byKey(inkWellKey),
matchesGoldenFile('m3_ink_well.renders.ink_splash.png'),
);
await gesture.up();
});
testWidgets('The InkWell widget renders an ink ripple', (WidgetTester tester) async {
const highlightColor = Color(0xAAFF0000);
const splashColor = Color(0xB40000FF);
const borderRadius = BorderRadius.all(Radius.circular(6.0));
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Center(
child: SizedBox(
width: 100.0,
height: 100.0,
child: InkWell(
borderRadius: borderRadius,
highlightColor: highlightColor,
splashColor: splashColor,
onTap: () {},
radius: 100.0,
splashFactory: InkRipple.splashFactory,
),
),
),
),
),
);
final Offset tapDownOffset = tester.getTopLeft(find.byType(InkWell));
final Offset inkWellCenter = tester.getCenter(find.byType(InkWell));
//final TestGesture gesture = await tester.startGesture(tapDownOffset);
await tester.tapAt(tapDownOffset);
await tester.pump(); // start gesture
final box = Material.of(tester.element(find.byType(InkWell))) as RenderBox;
bool offsetsAreClose(Offset a, Offset b) => (a - b).distance < 1.0;
bool radiiAreClose(double a, double b) => (a - b).abs() < 1.0;
PaintPattern ripplePattern(Offset expectedCenter, double expectedRadius, int expectedAlpha) {
return paints
..translate(x: 0.0, y: 0.0)
..translate(x: tapDownOffset.dx, y: tapDownOffset.dy)
..something((Symbol method, List<dynamic> arguments) {
if (method != #drawCircle) {
return false;
}
final center = arguments[0] as Offset;
final radius = arguments[1] as double;
final paint = arguments[2] as Paint;
if (offsetsAreClose(center, expectedCenter) &&
radiiAreClose(radius, expectedRadius) &&
paint.color.alpha == expectedAlpha) {
return true;
}
throw '''
Expected: center == $expectedCenter, radius == $expectedRadius, alpha == $expectedAlpha
Found: center == $center radius == $radius alpha == ${paint.color.alpha}''';
});
}
// Initially the ripple's center is where the tap occurred;
// ripplePattern always add a translation of tapDownOffset.
expect(box, ripplePattern(Offset.zero, 30.0, 0));
// The ripple fades in for 75ms. During that time its alpha is eased from
// 0 to the splashColor's alpha value and its center moves towards the
// center of the ink well.
await tester.pump(const Duration(milliseconds: 50));
expect(box, ripplePattern(const Offset(17.0, 17.0), 56.0, 120));
// At 75ms the ripple has fade in: it's alpha matches the splashColor's
// alpha and its center has moved closer to the ink well's center.
await tester.pump(const Duration(milliseconds: 25));
expect(box, ripplePattern(const Offset(29.0, 29.0), 73.0, 180));
// At this point the splash radius has expanded to its limit: 5 past the
// ink well's radius parameter. The splash center has moved to its final
// location at the inkwell's center and the fade-out is about to start.
// The fade-out begins at 225ms = 50ms + 25ms + 150ms.
await tester.pump(const Duration(milliseconds: 150));
expect(box, ripplePattern(inkWellCenter - tapDownOffset, 105.0, 180));
// After another 150ms the fade-out is complete.
await tester.pump(const Duration(milliseconds: 150));
expect(box, ripplePattern(inkWellCenter - tapDownOffset, 105.0, 0));
});
testWidgets('Material2 - Does the Ink widget render anything', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
home: Material(
child: Center(
child: Ink(
color: Colors.blue,
width: 200.0,
height: 200.0,
child: InkWell(splashColor: Colors.green, onTap: () {}),
),
),
),
),
);
final Offset center = tester.getCenter(find.byType(InkWell));
final TestGesture gesture = await tester.startGesture(center);
await tester.pump(); // start gesture
await tester.pump(const Duration(milliseconds: 200)); // wait for splash to be well under way
final box = Material.of(tester.element(find.byType(InkWell))) as RenderBox;
expect(
box,
paints
..rect(
rect: const Rect.fromLTRB(300.0, 200.0, 500.0, 400.0),
color: Color(Colors.blue.value),
)
..circle(color: Color(Colors.green.value)),
);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
home: Material(
child: Center(
child: Ink(
color: Colors.red,
width: 200.0,
height: 200.0,
child: InkWell(splashColor: Colors.green, onTap: () {}),
),
),
),
),
);
expect(Material.of(tester.element(find.byType(InkWell))), same(box));
expect(
box,
paints
..rect(
rect: const Rect.fromLTRB(300.0, 200.0, 500.0, 400.0),
color: Color(Colors.red.value),
)
..circle(color: Color(Colors.green.value)),
);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
home: Material(
child: Center(
child: InkWell(
// this is at a different depth in the tree so it's now a new InkWell
splashColor: Colors.green,
onTap: () {},
),
),
),
),
);
expect(Material.of(tester.element(find.byType(InkWell))), same(box));
expect(box, isNot(paints..rect()));
expect(box, isNot(paints..circle()));
await gesture.up();
});
testWidgets('Material3 - Does the Ink widget render anything', (WidgetTester tester) async {
const inkWellKey = Key('InkWell');
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: Ink(
color: Colors.blue,
width: 200.0,
height: 200.0,
child: InkWell(key: inkWellKey, splashColor: Colors.green, onTap: () {}),
),
),
),
),
);
final Offset center = tester.getCenter(find.byType(InkWell));
final TestGesture gesture = await tester.startGesture(center);
await tester.pump(); // start gesture
await tester.pump(const Duration(milliseconds: 200)); // wait for splash to be well under way
final box = Material.of(tester.element(find.byType(InkWell))) as RenderBox;
expect(
box,
paints..rect(
rect: const Rect.fromLTRB(300.0, 200.0, 500.0, 400.0),
color: Color(Colors.blue.value),
),
);
// Material 3 uses the InkSparkle which uses a shader, so we can't capture
// the effect with paint methods. Use a golden test instead.
await expectLater(find.byKey(inkWellKey), matchesGoldenFile('m3_ink.renders.anything.0.png'));
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: Ink(
color: Colors.red,
width: 200.0,
height: 200.0,
child: InkWell(key: inkWellKey, splashColor: Colors.green, onTap: () {}),
),
),
),
),
);
expect(Material.of(tester.element(find.byType(InkWell))), same(box));
expect(
box,
paints..rect(
rect: const Rect.fromLTRB(300.0, 200.0, 500.0, 400.0),
color: Color(Colors.red.value),
),
);
// Material 3 uses the InkSparkle which uses a shader, so we can't capture
// the effect with paint methods. Use a golden test instead.
await expectLater(find.byKey(inkWellKey), matchesGoldenFile('m3_ink.renders.anything.1.png'));
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: InkWell(
// This is at a different depth in the tree so it's now a new InkWell.
key: inkWellKey,
splashColor: Colors.green,
onTap: () {},
),
),
),
),
);
expect(Material.of(tester.element(find.byType(InkWell))), same(box));
expect(box, isNot(paints..rect()));
expect(box, isNot(paints..rect()));
await gesture.up();
});
testWidgets('The InkWell widget renders an SelectAction or ActivateAction-induced ink ripple', (
WidgetTester tester,
) async {
const highlightColor = Color(0xAAFF0000);
const splashColor = Color(0xB40000FF);
const borderRadius = BorderRadius.all(Radius.circular(6.0));
final focusNode = FocusNode(debugLabel: 'Test Node');
addTearDown(focusNode.dispose);
Future<void> buildTest(Intent intent) async {
return tester.pumpWidget(
Shortcuts(
shortcuts: <ShortcutActivator, Intent>{
const SingleActivator(LogicalKeyboardKey.space): intent,
},
child: Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Center(
child: SizedBox(
width: 100.0,
height: 100.0,
child: InkWell(
borderRadius: borderRadius,
highlightColor: highlightColor,
splashColor: splashColor,
focusNode: focusNode,
onTap: () {},
radius: 100.0,
splashFactory: InkRipple.splashFactory,
),
),
),
),
),
),
);
}
await buildTest(const ActivateIntent());
focusNode.requestFocus();
await tester.pumpAndSettle();
final Offset topLeft = tester.getTopLeft(find.byType(InkWell));
final Offset inkWellCenter = tester.getCenter(find.byType(InkWell)) - topLeft;
bool offsetsAreClose(Offset a, Offset b) => (a - b).distance < 1.0;
bool radiiAreClose(double a, double b) => (a - b).abs() < 1.0;
PaintPattern ripplePattern(double expectedRadius, int expectedAlpha) {
return paints
..translate(x: 0.0, y: 0.0)
..translate(x: topLeft.dx, y: topLeft.dy)
..something((Symbol method, List<dynamic> arguments) {
if (method != #drawCircle) {
return false;
}
final center = arguments[0] as Offset;
final radius = arguments[1] as double;
final paint = arguments[2] as Paint;
if (offsetsAreClose(center, inkWellCenter) &&
radiiAreClose(radius, expectedRadius) &&
paint.color.alpha == expectedAlpha) {
return true;
}
throw '''
Expected: center == $inkWellCenter, radius == $expectedRadius, alpha == $expectedAlpha
Found: center == $center radius == $radius alpha == ${paint.color.alpha}''';
});
}
await buildTest(const ActivateIntent());
await tester.pumpAndSettle();
await tester.sendKeyEvent(LogicalKeyboardKey.space);
await tester.pump();
final box = Material.of(tester.element(find.byType(InkWell))) as RenderBox;
// ripplePattern always add a translation of topLeft.
expect(box, ripplePattern(30.0, 0));
// The ripple fades in for 75ms. During that time its alpha is eased from
// 0 to the splashColor's alpha value.
await tester.pump(const Duration(milliseconds: 50));
expect(box, ripplePattern(56.0, 120));
// At 75ms the ripple has faded in: it's alpha matches the splashColor's
// alpha.
await tester.pump(const Duration(milliseconds: 25));
expect(box, ripplePattern(73.0, 180));
// At this point the splash radius has expanded to its limit: 5 past the
// ink well's radius parameter. The fade-out is about to start.
// The fade-out begins at 225ms = 50ms + 25ms + 150ms.
await tester.pump(const Duration(milliseconds: 150));
expect(box, ripplePattern(105.0, 180));
// After another 150ms the fade-out is complete.
await tester.pump(const Duration(milliseconds: 150));
expect(box, ripplePattern(105.0, 0));
});
testWidgets('Cancel an InkRipple that was disposed when its animation ended', (
WidgetTester tester,
) async {
// Regression test for https://github.com/flutter/flutter/issues/14391
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Center(
child: SizedBox(
width: 100.0,
height: 100.0,
child: InkWell(onTap: () {}, radius: 100.0, splashFactory: InkRipple.splashFactory),
),
),
),
),
);
final Offset tapDownOffset = tester.getTopLeft(find.byType(InkWell));
await tester.tapAt(tapDownOffset);
await tester.pump(); // start splash
await tester.pump(const Duration(milliseconds: 375)); // _kFadeOutDuration, in_ripple.dart
final TestGesture gesture = await tester.startGesture(tapDownOffset);
await tester.pump(); // start gesture
await gesture.moveTo(Offset.zero);
await gesture.up(); // generates a tap cancel
await tester.pumpAndSettle();
});
testWidgets('Cancel an InkRipple that was disposed when its animation ended', (
WidgetTester tester,
) async {
const highlightColor = Color(0xAAFF0000);
const splashColor = Color(0xB40000FF);
// Regression test for https://github.com/flutter/flutter/issues/14391
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Center(
child: SizedBox(
width: 100.0,
height: 100.0,
child: InkWell(
splashColor: splashColor,
highlightColor: highlightColor,
onTap: () {},
radius: 100.0,
splashFactory: InkRipple.splashFactory,
),
),
),
),
),
);
final Offset tapDownOffset = tester.getTopLeft(find.byType(InkWell));
await tester.tapAt(tapDownOffset);
await tester.pump(); // start splash
// No delay here so _fadeInController.value=1.0 (InkRipple.dart)
// Generate a tap cancel; Will cancel the ink splash before it started
final TestGesture gesture = await tester.startGesture(tapDownOffset);
await tester.pump(); // start gesture
await gesture.moveTo(Offset.zero);
await gesture.up(); // generates a tap cancel
final box = Material.of(tester.element(find.byType(InkWell))) as RenderBox;
expect(
box,
paints..everything((Symbol method, List<dynamic> arguments) {
if (method != #drawCircle) {
return true;
}
final paint = arguments[2] as Paint;
if (paint.color.alpha == 0) {
return true;
}
throw 'Expected: paint.color.alpha == 0, found: ${paint.color.alpha}';
}),
);
});
testWidgets('The InkWell widget on OverlayPortal does not throw', (WidgetTester tester) async {
final controller = OverlayPortalController();
controller.show();
late OverlayEntry overlayEntry;
addTearDown(
() => overlayEntry
..remove()
..dispose(),
);
await tester.pumpWidget(
Center(
child: RepaintBoundary(
child: SizedBox.square(
dimension: 200,
child: Directionality(
textDirection: TextDirection.ltr,
child: Overlay(
initialEntries: <OverlayEntry>[
overlayEntry = OverlayEntry(
builder: (BuildContext context) {
return Center(
child: SizedBox.square(
dimension: 100,
// The material partially overlaps the overlayChild.
// This is to verify that the `overlayChild`'s ink
// features aren't clipped by it.
child: Material(
color: Colors.black,
child: OverlayPortal(
controller: controller,
overlayChildBuilder: (BuildContext context) {
return Positioned(
right: 0,
bottom: 0,
child: InkWell(
splashColor: Colors.red,
onTap: () {},
child: const SizedBox.square(dimension: 100),
),
);
},
),
),
),
);
},
),
],
),
),
),
),
),
);
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(InkWell)));
addTearDown(() async {
await gesture.up();
});
await tester.pump(); // start gesture
await tester.pump(const Duration(seconds: 2));
expect(tester.takeException(), isNull);
});
testWidgets('Material2 - Custom rectCallback renders an ink splash from its center', (
WidgetTester tester,
) async {
const splashColor = Color(0xff00ff00);
Widget buildWidget({InteractiveInkFeatureFactory? splashFactory}) {
return MaterialApp(
theme: ThemeData(useMaterial3: false),
home: Material(
child: Center(
child: SizedBox(
width: 100.0,
height: 200.0,
child: InkResponse(
splashColor: splashColor,
containedInkWell: true,
highlightShape: BoxShape.rectangle,
splashFactory: splashFactory,
onTap: () {},
),
),
),
),
);
}
await tester.pumpWidget(buildWidget());
final Offset center = tester.getCenter(find.byType(SizedBox));
TestGesture gesture = await tester.startGesture(center);
await tester.pump(); // start gesture
await tester.pumpAndSettle(); // Finish rendering ink splash.
var box = Material.of(tester.element(find.byType(InkResponse))) as RenderBox;
expect(box, paints..circle(x: 50.0, y: 100.0, color: splashColor));
await gesture.up();
await tester.pumpWidget(buildWidget(splashFactory: _InkRippleFactory()));
await tester.pumpAndSettle(); // Finish rendering ink splash.
gesture = await tester.startGesture(center);
await tester.pump(); // start gesture
await tester.pumpAndSettle(); // Finish rendering ink splash.
box = Material.of(tester.element(find.byType(InkResponse))) as RenderBox;
expect(box, paints..circle(x: 50.0, y: 50.0, color: splashColor));
});
testWidgets('Material3 - Custom rectCallback renders an ink splash from its center', (
WidgetTester tester,
) async {
const inkWResponseKey = Key('InkResponse');
const splashColor = Color(0xff00ff00);
Widget buildWidget({InteractiveInkFeatureFactory? splashFactory}) {
return MaterialApp(
home: Material(
child: Center(
child: SizedBox(
width: 100.0,
height: 200.0,
child: InkResponse(
key: inkWResponseKey,
splashColor: splashColor,
containedInkWell: true,
highlightShape: BoxShape.rectangle,
splashFactory: splashFactory,
onTap: () {},
),
),
),
),
);
}
await tester.pumpWidget(buildWidget());
final Offset center = tester.getCenter(find.byType(SizedBox));
TestGesture gesture = await tester.startGesture(center);
await tester.pump(); // start gesture
await tester.pumpAndSettle(); // Finish rendering ink splash.
// Material 3 uses the InkSparkle which uses a shader, so we can't capture
// the effect with paint methods. Use a golden test instead.
await expectLater(
find.byKey(inkWResponseKey),
matchesGoldenFile('m3_ink_response.renders.ink_splash_from_its_center.0.png'),
);
await gesture.up();
await tester.pumpWidget(buildWidget(splashFactory: _InkRippleFactory()));
await tester.pumpAndSettle(); // Finish rendering ink splash.
gesture = await tester.startGesture(center);
await tester.pump(); // start gesture
await tester.pumpAndSettle(); // Finish rendering ink splash.
// Material 3 uses the InkSparkle which uses a shader, so we can't capture
// the effect with paint methods. Use a golden test instead.
await expectLater(
find.byKey(inkWResponseKey),
matchesGoldenFile('m3_ink_response.renders.ink_splash_from_its_center.1.png'),
);
});
testWidgets('Ink with isVisible=false does not paint', (WidgetTester tester) async {
const testColor = Color(0xffff1234);
Widget inkWidget({required bool isVisible}) {
return Material(
child: Visibility.maintain(
visible: isVisible,
child: Ink(decoration: const BoxDecoration(color: testColor)),
),
);
}
await tester.pumpWidget(inkWidget(isVisible: true));
RenderBox box = tester.renderObject(find.byType(Material));
expect(box, paints..rect(color: testColor));
await tester.pumpWidget(inkWidget(isVisible: false));
box = tester.renderObject(find.byType(Material));
expect(box, isNot(paints..rect(color: testColor)));
});
}
class _InkRippleFactory extends InteractiveInkFeatureFactory {
@override
InteractiveInkFeature create({
required MaterialInkController controller,
required RenderBox referenceBox,
required Offset position,
required Color color,
required TextDirection textDirection,
bool containedInkWell = false,
RectCallback? rectCallback,
BorderRadius? borderRadius,
ShapeBorder? customBorder,
double? radius,
VoidCallback? onRemoved,
}) {
return InkRipple(
controller: controller,
referenceBox: referenceBox,
position: position,
color: color,
containedInkWell: containedInkWell,
rectCallback: () => Offset.zero & const Size(100, 100),
borderRadius: borderRadius,
customBorder: customBorder,
radius: radius,
onRemoved: onRemoved,
textDirection: textDirection,
);
}
}