blob: 0f4861068b780126513dac886d10c1a9f5b11137 [file] [log] [blame]
// 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.
// reduced-test-set:
// This file is run as part of a reduced test set in CI on Mac and Windows
// machines.
@Tags(<String>['reduced-test-set'])
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
import 'test_border.dart' show TestBorder;
final List<String> log = <String>[];
class PathClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
log.add('getClip');
return Path()
..addRect(const Rect.fromLTWH(50.0, 50.0, 100.0, 100.0));
}
@override
bool shouldReclip(PathClipper oldClipper) => false;
}
class ValueClipper<T> extends CustomClipper<T> {
ValueClipper(this.message, this.value);
final String message;
final T value;
@override
T getClip(Size size) {
log.add(message);
return value;
}
@override
bool shouldReclip(ValueClipper<T> oldClipper) {
return oldClipper.message != message || oldClipper.value != value;
}
}
class NotifyClipper<T> extends CustomClipper<T> {
NotifyClipper({required this.clip}) : super(reclip: clip);
final ValueNotifier<T> clip;
@override
T getClip(Size size) => clip.value;
@override
bool shouldReclip(NotifyClipper<T> oldClipper) => clip != oldClipper.clip;
}
void main() {
testWidgets('ClipRect with a FittedBox child sized to zero works with semantics', (WidgetTester tester) async {
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: ClipRect(
child: FittedBox(
child: SizedBox.fromSize(
size: Size.zero,
child: Semantics(
image: true,
label: 'Image',
),
),
),
),
),
);
expect(find.byType(FittedBox), findsOneWidget);
});
testWidgets('ClipRect updates clipBehavior in updateRenderObject', (WidgetTester tester) async {
await tester.pumpWidget(const ClipRect());
final RenderClipRect renderClip = tester.allRenderObjects.whereType<RenderClipRect>().first;
expect(renderClip.clipBehavior, equals(Clip.hardEdge));
await tester.pumpWidget(const ClipRect(clipBehavior: Clip.antiAlias));
expect(renderClip.clipBehavior, equals(Clip.antiAlias));
});
test('ClipRRect constructs with the right default values', () {
const ClipRRect clipRRect = ClipRRect();
expect(clipRRect.clipBehavior, equals(Clip.antiAlias));
expect(clipRRect.borderRadius, equals(BorderRadius.zero));
});
testWidgets('ClipRRect updates clipBehavior in updateRenderObject', (WidgetTester tester) async {
await tester.pumpWidget(const ClipRRect());
final RenderClipRRect renderClip = tester.allRenderObjects.whereType<RenderClipRRect>().first;
expect(renderClip.clipBehavior, equals(Clip.antiAlias));
await tester.pumpWidget(const ClipRRect(clipBehavior: Clip.hardEdge));
expect(renderClip.clipBehavior, equals(Clip.hardEdge));
});
testWidgets('ClipOval updates clipBehavior in updateRenderObject', (WidgetTester tester) async {
await tester.pumpWidget(const ClipOval());
final RenderClipOval renderClip = tester.allRenderObjects.whereType<RenderClipOval>().first;
expect(renderClip.clipBehavior, equals(Clip.antiAlias));
await tester.pumpWidget(const ClipOval(clipBehavior: Clip.hardEdge));
expect(renderClip.clipBehavior, equals(Clip.hardEdge));
});
testWidgets('ClipPath updates clipBehavior in updateRenderObject', (WidgetTester tester) async {
await tester.pumpWidget(const ClipPath());
final RenderClipPath renderClip = tester.allRenderObjects.whereType<RenderClipPath>().first;
expect(renderClip.clipBehavior, equals(Clip.antiAlias));
await tester.pumpWidget(const ClipPath(clipBehavior: Clip.hardEdge));
expect(renderClip.clipBehavior, equals(Clip.hardEdge));
});
testWidgets('ClipPath', (WidgetTester tester) async {
await tester.pumpWidget(
ClipPath(
clipper: PathClipper(),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () { log.add('tap'); },
),
),
);
expect(log, equals(<String>['getClip']));
await tester.tapAt(const Offset(10.0, 10.0));
expect(log, equals(<String>['getClip']));
log.clear();
await tester.tapAt(const Offset(100.0, 100.0));
expect(log, equals(<String>['tap']));
log.clear();
});
testWidgets('ClipOval', (WidgetTester tester) async {
await tester.pumpWidget(
ClipOval(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () { log.add('tap'); },
),
),
);
expect(log, equals(<String>[]));
await tester.tapAt(const Offset(10.0, 10.0));
expect(log, equals(<String>[]));
log.clear();
await tester.tapAt(const Offset(400.0, 300.0));
expect(log, equals(<String>['tap']));
log.clear();
});
testWidgets('Transparent ClipOval hit test', (WidgetTester tester) async {
await tester.pumpWidget(
Opacity(
opacity: 0.0,
child: ClipOval(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () { log.add('tap'); },
),
),
),
);
expect(log, equals(<String>[]));
await tester.tapAt(const Offset(10.0, 10.0));
expect(log, equals(<String>[]));
log.clear();
await tester.tapAt(const Offset(400.0, 300.0));
expect(log, equals(<String>['tap']));
log.clear();
});
testWidgets('ClipRect', (WidgetTester tester) async {
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 100.0,
height: 100.0,
child: ClipRect(
clipper: ValueClipper<Rect>('a', const Rect.fromLTWH(5.0, 5.0, 10.0, 10.0)),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () { log.add('tap'); },
),
),
),
),
);
expect(log, equals(<String>['a']));
await tester.tapAt(const Offset(10.0, 10.0));
expect(log, equals(<String>['a', 'tap']));
await tester.tapAt(const Offset(100.0, 100.0));
expect(log, equals(<String>['a', 'tap']));
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 100.0,
height: 100.0,
child: ClipRect(
clipper: ValueClipper<Rect>('a', const Rect.fromLTWH(5.0, 5.0, 10.0, 10.0)),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () { log.add('tap'); },
),
),
),
),
);
expect(log, equals(<String>['a', 'tap']));
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 200.0,
height: 200.0,
child: ClipRect(
clipper: ValueClipper<Rect>('a', const Rect.fromLTWH(5.0, 5.0, 10.0, 10.0)),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () { log.add('tap'); },
),
),
),
),
);
expect(log, equals(<String>['a', 'tap', 'a']));
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 200.0,
height: 200.0,
child: ClipRect(
clipper: ValueClipper<Rect>('a', const Rect.fromLTWH(5.0, 5.0, 10.0, 10.0)),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () { log.add('tap'); },
),
),
),
),
);
expect(log, equals(<String>['a', 'tap', 'a']));
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 200.0,
height: 200.0,
child: ClipRect(
clipper: ValueClipper<Rect>('b', const Rect.fromLTWH(5.0, 5.0, 10.0, 10.0)),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () { log.add('tap'); },
),
),
),
),
);
expect(log, equals(<String>['a', 'tap', 'a', 'b']));
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 200.0,
height: 200.0,
child: ClipRect(
clipper: ValueClipper<Rect>('c', const Rect.fromLTWH(25.0, 25.0, 10.0, 10.0)),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () { log.add('tap'); },
),
),
),
),
);
expect(log, equals(<String>['a', 'tap', 'a', 'b', 'c']));
await tester.tapAt(const Offset(30.0, 30.0));
expect(log, equals(<String>['a', 'tap', 'a', 'b', 'c', 'tap']));
await tester.tapAt(const Offset(100.0, 100.0));
expect(log, equals(<String>['a', 'tap', 'a', 'b', 'c', 'tap']));
log.clear();
});
testWidgets('debugPaintSizeEnabled', (WidgetTester tester) async {
await tester.pumpWidget(
const ClipRect(
child: Placeholder(),
),
);
expect(tester.renderObject(find.byType(ClipRect)).paint, paints
..save()
..clipRect(rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 600.0))
..save()
..path() // Placeholder
..restore()
..restore(),
);
debugPaintSizeEnabled = true;
expect(tester.renderObject(find.byType(ClipRect)).debugPaint, paints
..rect(rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 600.0))
..paragraph(),
);
debugPaintSizeEnabled = false;
});
testWidgets('ClipRect painting', (WidgetTester tester) async {
await tester.pumpWidget(
Center(
child: RepaintBoundary(
child: Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(100.0),
child: SizedBox(
height: 100.0,
width: 100.0,
child: Transform.rotate(
angle: 1.0, // radians
child: ClipRect(
child: Container(
color: Colors.red,
child: Container(
color: Colors.white,
child: RepaintBoundary(
child: Center(
child: Container(
color: Colors.black,
height: 10.0,
width: 10.0,
),
),
),
),
),
),
),
),
),
),
),
),
);
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.ClipRect.png'),
);
});
testWidgets('ClipRect save, overlay, and antialiasing', (WidgetTester tester) async {
await tester.pumpWidget(
RepaintBoundary(
child: Stack(
textDirection: TextDirection.ltr,
children: <Widget>[
Positioned(
top: 0.0,
left: 0.0,
width: 100.0,
height: 100.0,
child: ClipRect(
child: Container(
color: Colors.blue,
),
),
),
Positioned(
top: 50.0,
left: 50.0,
width: 100.0,
height: 100.0,
child: Transform.rotate(
angle: 1.0,
child: Container(
color: Colors.red,
),
),
),
],
),
),
);
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.ClipRectOverlay.png'),
);
});
testWidgets('ClipRRect painting', (WidgetTester tester) async {
await tester.pumpWidget(
Center(
child: RepaintBoundary(
child: Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(100.0),
child: SizedBox(
height: 100.0,
width: 100.0,
child: Transform.rotate(
angle: 1.0, // radians
child: ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.elliptical(10.0, 20.0),
topRight: Radius.elliptical(5.0, 30.0),
bottomLeft: Radius.elliptical(2.5, 12.0),
bottomRight: Radius.elliptical(15.0, 6.0),
),
child: Container(
color: Colors.red,
child: Container(
color: Colors.white,
child: RepaintBoundary(
child: Center(
child: Container(
color: Colors.black,
height: 10.0,
width: 10.0,
),
),
),
),
),
),
),
),
),
),
),
),
);
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.ClipRRect.png'),
);
});
testWidgets('ClipOval painting', (WidgetTester tester) async {
await tester.pumpWidget(
Center(
child: RepaintBoundary(
child: Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(100.0),
child: SizedBox(
height: 100.0,
width: 100.0,
child: Transform.rotate(
angle: 1.0, // radians
child: ClipOval(
child: Container(
color: Colors.red,
child: Container(
color: Colors.white,
child: RepaintBoundary(
child: Center(
child: Container(
color: Colors.black,
height: 10.0,
width: 10.0,
),
),
),
),
),
),
),
),
),
),
),
),
);
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.ClipOval.png'),
);
});
testWidgets('ClipPath painting', (WidgetTester tester) async {
await tester.pumpWidget(
Center(
child: RepaintBoundary(
child: Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(100.0),
child: SizedBox(
height: 100.0,
width: 100.0,
child: Transform.rotate(
angle: 1.0, // radians
child: ClipPath(
clipper: const ShapeBorderClipper(
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0)),
),
),
child: Container(
color: Colors.red,
child: Container(
color: Colors.white,
child: RepaintBoundary(
child: Center(
child: Container(
color: Colors.black,
height: 10.0,
width: 10.0,
),
),
),
),
),
),
),
),
),
),
),
),
);
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.ClipPath.png'),
);
});
Center genPhysicalModel(Clip clipBehavior) {
return Center(
child: RepaintBoundary(
child: Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(100.0),
child: SizedBox(
height: 100.0,
width: 100.0,
child: Transform.rotate(
angle: 1.0, // radians
child: PhysicalModel(
borderRadius: const BorderRadius.all(Radius.circular(20.0)),
color: Colors.red,
clipBehavior: clipBehavior,
child: Container(
color: Colors.white,
child: RepaintBoundary(
child: Center(
child: Container(
color: Colors.black,
height: 10.0,
width: 10.0,
),
),
),
),
),
),
),
),
),
),
);
}
testWidgets('PhysicalModel painting with Clip.antiAlias', (WidgetTester tester) async {
await tester.pumpWidget(genPhysicalModel(Clip.antiAlias));
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.PhysicalModel.antiAlias.png'),
);
});
testWidgets('PhysicalModel painting with Clip.hardEdge', (WidgetTester tester) async {
await tester.pumpWidget(genPhysicalModel(Clip.hardEdge));
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.PhysicalModel.hardEdge.png'),
);
});
// There will be bleeding edges on the rect edges, but there shouldn't be any bleeding edges on the
// round corners.
testWidgets('PhysicalModel painting with Clip.antiAliasWithSaveLayer', (WidgetTester tester) async {
await tester.pumpWidget(genPhysicalModel(Clip.antiAliasWithSaveLayer));
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.PhysicalModel.antiAliasWithSaveLayer.png'),
);
});
testWidgets('Default PhysicalModel painting', (WidgetTester tester) async {
await tester.pumpWidget(
Center(
child: RepaintBoundary(
child: Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(100.0),
child: SizedBox(
height: 100.0,
width: 100.0,
child: Transform.rotate(
angle: 1.0, // radians
child: PhysicalModel(
borderRadius: const BorderRadius.all(Radius.circular(20.0)),
color: Colors.red,
child: Container(
color: Colors.white,
child: RepaintBoundary(
child: Center(
child: Container(
color: Colors.black,
height: 10.0,
width: 10.0,
),
),
),
),
),
),
),
),
),
),
),
);
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.PhysicalModel.default.png'),
);
});
Center genPhysicalShape(Clip clipBehavior) {
return Center(
child: RepaintBoundary(
child: Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(100.0),
child: SizedBox(
height: 100.0,
width: 100.0,
child: Transform.rotate(
angle: 1.0, // radians
child: PhysicalShape(
clipper: const ShapeBorderClipper(
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0)),
),
),
clipBehavior: clipBehavior,
color: Colors.red,
child: Container(
color: Colors.white,
child: RepaintBoundary(
child: Center(
child: Container(
color: Colors.black,
height: 10.0,
width: 10.0,
),
),
),
),
),
),
),
),
),
),
);
}
testWidgets('PhysicalShape painting with Clip.antiAlias', (WidgetTester tester) async {
await tester.pumpWidget(genPhysicalShape(Clip.antiAlias));
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.PhysicalShape.antiAlias.png'),
);
});
testWidgets('PhysicalShape painting with Clip.hardEdge', (WidgetTester tester) async {
await tester.pumpWidget(genPhysicalShape(Clip.hardEdge));
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.PhysicalShape.hardEdge.png'),
);
});
testWidgets('PhysicalShape painting with Clip.antiAliasWithSaveLayer', (WidgetTester tester) async {
await tester.pumpWidget(genPhysicalShape(Clip.antiAliasWithSaveLayer));
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.PhysicalShape.antiAliasWithSaveLayer.png'),
);
});
testWidgets('PhysicalShape painting', (WidgetTester tester) async {
await tester.pumpWidget(
Center(
child: RepaintBoundary(
child: Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(100.0),
child: SizedBox(
height: 100.0,
width: 100.0,
child: Transform.rotate(
angle: 1.0, // radians
child: PhysicalShape(
clipper: const ShapeBorderClipper(
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0)),
),
),
color: Colors.red,
child: Container(
color: Colors.white,
child: RepaintBoundary(
child: Center(
child: Container(
color: Colors.black,
height: 10.0,
width: 10.0,
),
),
),
),
),
),
),
),
),
),
),
);
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.PhysicalShape.default.png'),
);
});
testWidgets('ClipPath.shape', (WidgetTester tester) async {
final List<String> logs = <String>[];
final ShapeBorder shape = TestBorder((String message) { logs.add(message); });
Widget buildClipPath() {
return ClipPath.shape(
shape: shape,
child: const SizedBox(width: 100.0, height: 100.0),
);
}
final Widget clipPath = buildClipPath();
// verify that a regular clip works as one would expect
logs.add('--0');
await tester.pumpWidget(clipPath);
// verify that pumping again doesn't recompute the clip
// even though the widget itself is new (the shape doesn't change identity)
logs.add('--1');
await tester.pumpWidget(buildClipPath());
// verify that ClipPath passes the TextDirection on to its shape
logs.add('--2');
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: clipPath,
));
// verify that changing the text direction from LTR to RTL has an effect
// even though the widget itself is identical
logs.add('--3');
await tester.pumpWidget(Directionality(
textDirection: TextDirection.rtl,
child: clipPath,
));
// verify that pumping again with a text direction has no effect
logs.add('--4');
await tester.pumpWidget(Directionality(
textDirection: TextDirection.rtl,
child: buildClipPath(),
));
logs.add('--5');
// verify that changing the text direction and the widget at the same time
// works as expected
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: clipPath,
));
expect(logs, <String>[
'--0',
'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) null',
'--1',
'--2',
'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.ltr',
'--3',
'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.rtl',
'--4',
'--5',
'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.ltr',
]);
});
testWidgets('CustomClipper reclips when notified', (WidgetTester tester) async {
final ValueNotifier<Rect> clip = ValueNotifier<Rect>(const Rect.fromLTWH(50.0, 50.0, 100.0, 100.0));
await tester.pumpWidget(
ClipRect(
clipper: NotifyClipper<Rect>(clip: clip),
child: const Placeholder(),
),
);
expect(tester.renderObject(find.byType(ClipRect)).paint, paints
..save()
..clipRect(rect: const Rect.fromLTWH(50.0, 50.0, 100.0, 100.0))
..save()
..path() // Placeholder
..restore()
..restore(),
);
expect(tester.renderObject(find.byType(ClipRect)).debugNeedsPaint, isFalse);
clip.value = const Rect.fromLTWH(50.0, 50.0, 150.0, 100.0);
expect(tester.renderObject(find.byType(ClipRect)).debugNeedsPaint, isTrue);
expect(tester.renderObject(find.byType(ClipRect)).paint, paints
..save()
..clipRect(rect: const Rect.fromLTWH(50.0, 50.0, 150.0, 100.0))
..save()
..path() // Placeholder
..restore()
..restore(),
);
});
}