| // 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. |
| |
| import 'dart:typed_data'; |
| import 'dart:ui' as ui show Gradient, Image, ImageFilter; |
| |
| import 'package:flutter/animation.dart'; |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/gestures.dart'; |
| import 'package:flutter/rendering.dart'; |
| import '../flutter_test_alternative.dart'; |
| |
| import 'rendering_tester.dart'; |
| |
| void main() { |
| test('RenderFittedBox handles applying paint transform and hit-testing with empty size', () { |
| final RenderFittedBox fittedBox = RenderFittedBox( |
| child: RenderCustomPaint( |
| preferredSize: Size.zero, |
| painter: TestCallbackPainter(onPaint: () {}), |
| ), |
| ); |
| |
| layout(fittedBox, phase: EnginePhase.flushSemantics); |
| final Matrix4 transform = Matrix4.identity(); |
| fittedBox.applyPaintTransform(fittedBox.child, transform); |
| expect(transform, Matrix4.zero()); |
| |
| final BoxHitTestResult hitTestResult = BoxHitTestResult(); |
| expect(fittedBox.hitTestChildren(hitTestResult), isFalse); |
| }); |
| |
| test('RenderFittedBox does not paint with empty sizes', () { |
| bool painted; |
| RenderFittedBox makeFittedBox(Size size) { |
| return RenderFittedBox( |
| child: RenderCustomPaint( |
| preferredSize: size, |
| painter: TestCallbackPainter(onPaint: () { |
| painted = true; |
| }), |
| ), |
| ); |
| } |
| |
| // The RenderFittedBox paints if both its size and its child's size are nonempty. |
| painted = false; |
| layout(makeFittedBox(const Size(1, 1)), phase: EnginePhase.paint); |
| expect(painted, equals(true)); |
| |
| // The RenderFittedBox should not paint if its child is empty-sized. |
| painted = false; |
| layout(makeFittedBox(Size.zero), phase: EnginePhase.paint); |
| expect(painted, equals(false)); |
| |
| // The RenderFittedBox should not paint if it is empty. |
| painted = false; |
| layout(makeFittedBox(const Size(1, 1)), constraints: BoxConstraints.tight(Size.zero), phase: EnginePhase.paint); |
| expect(painted, equals(false)); |
| }); |
| |
| test('RenderPhysicalModel compositing on Fuchsia', () { |
| debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia; |
| |
| final RenderPhysicalModel root = RenderPhysicalModel(color: const Color(0xffff00ff)); |
| layout(root, phase: EnginePhase.composite); |
| expect(root.needsCompositing, isTrue); |
| |
| // On Fuchsia, the system compositor is responsible for drawing shadows |
| // for physical model layers with non-zero elevation. |
| root.elevation = 1.0; |
| pumpFrame(phase: EnginePhase.composite); |
| expect(root.needsCompositing, isTrue); |
| |
| root.elevation = 0.0; |
| pumpFrame(phase: EnginePhase.composite); |
| expect(root.needsCompositing, isTrue); |
| |
| debugDefaultTargetPlatformOverride = null; |
| }); |
| |
| test('RenderPhysicalModel compositing on non-Fuchsia', () { |
| for (final TargetPlatform platform in TargetPlatform.values) { |
| if (platform == TargetPlatform.fuchsia) { |
| continue; |
| } |
| debugDefaultTargetPlatformOverride = platform; |
| |
| final RenderPhysicalModel root = RenderPhysicalModel(color: const Color(0xffff00ff)); |
| layout(root, phase: EnginePhase.composite); |
| expect(root.needsCompositing, isTrue); |
| |
| // Flutter now composites physical shapes on all platforms. |
| root.elevation = 1.0; |
| pumpFrame(phase: EnginePhase.composite); |
| expect(root.needsCompositing, isTrue); |
| |
| root.elevation = 0.0; |
| pumpFrame(phase: EnginePhase.composite); |
| expect(root.needsCompositing, isTrue); |
| } |
| debugDefaultTargetPlatformOverride = null; |
| }); |
| |
| test('RenderSemanticsGestureHandler adds/removes correct semantic actions', () { |
| final RenderSemanticsGestureHandler renderObj = RenderSemanticsGestureHandler( |
| onTap: () { }, |
| onHorizontalDragUpdate: (DragUpdateDetails details) { }, |
| ); |
| |
| SemanticsConfiguration config = SemanticsConfiguration(); |
| renderObj.describeSemanticsConfiguration(config); |
| expect(config.getActionHandler(SemanticsAction.tap), isNotNull); |
| expect(config.getActionHandler(SemanticsAction.scrollLeft), isNotNull); |
| expect(config.getActionHandler(SemanticsAction.scrollRight), isNotNull); |
| |
| config = SemanticsConfiguration(); |
| renderObj.validActions = <SemanticsAction>{SemanticsAction.tap, SemanticsAction.scrollLeft}; |
| |
| renderObj.describeSemanticsConfiguration(config); |
| expect(config.getActionHandler(SemanticsAction.tap), isNotNull); |
| expect(config.getActionHandler(SemanticsAction.scrollLeft), isNotNull); |
| expect(config.getActionHandler(SemanticsAction.scrollRight), isNull); |
| }); |
| |
| group('RenderPhysicalShape', () { |
| test('shape change triggers repaint', () { |
| for (final TargetPlatform platform in TargetPlatform.values) { |
| if (platform == TargetPlatform.fuchsia) { |
| continue; |
| } |
| debugDefaultTargetPlatformOverride = platform; |
| |
| final RenderPhysicalShape root = RenderPhysicalShape( |
| color: const Color(0xffff00ff), |
| clipper: const ShapeBorderClipper(shape: CircleBorder()), |
| ); |
| layout(root, phase: EnginePhase.composite); |
| expect(root.debugNeedsPaint, isFalse); |
| |
| // Same shape, no repaint. |
| root.clipper = const ShapeBorderClipper(shape: CircleBorder()); |
| expect(root.debugNeedsPaint, isFalse); |
| |
| // Different shape triggers repaint. |
| root.clipper = const ShapeBorderClipper(shape: StadiumBorder()); |
| expect(root.debugNeedsPaint, isTrue); |
| } |
| debugDefaultTargetPlatformOverride = null; |
| }); |
| |
| test('compositing on non-Fuchsia', () { |
| for (final TargetPlatform platform in TargetPlatform.values) { |
| if (platform == TargetPlatform.fuchsia) { |
| continue; |
| } |
| debugDefaultTargetPlatformOverride = platform; |
| final RenderPhysicalShape root = RenderPhysicalShape( |
| color: const Color(0xffff00ff), |
| clipper: const ShapeBorderClipper(shape: CircleBorder()), |
| ); |
| layout(root, phase: EnginePhase.composite); |
| expect(root.needsCompositing, isTrue); |
| |
| // On non-Fuchsia platforms, we composite physical shape layers |
| root.elevation = 1.0; |
| pumpFrame(phase: EnginePhase.composite); |
| expect(root.needsCompositing, isTrue); |
| |
| root.elevation = 0.0; |
| pumpFrame(phase: EnginePhase.composite); |
| expect(root.needsCompositing, isTrue); |
| } |
| debugDefaultTargetPlatformOverride = null; |
| }); |
| }); |
| |
| test('RenderRepaintBoundary can capture images of itself', () async { |
| RenderRepaintBoundary boundary = RenderRepaintBoundary(); |
| layout(boundary, constraints: BoxConstraints.tight(const Size(100.0, 200.0))); |
| pumpFrame(phase: EnginePhase.composite); |
| ui.Image image = await boundary.toImage(); |
| expect(image.width, equals(100)); |
| expect(image.height, equals(200)); |
| |
| // Now with pixel ratio set to something other than 1.0. |
| boundary = RenderRepaintBoundary(); |
| layout(boundary, constraints: BoxConstraints.tight(const Size(100.0, 200.0))); |
| pumpFrame(phase: EnginePhase.composite); |
| image = await boundary.toImage(pixelRatio: 2.0); |
| expect(image.width, equals(200)); |
| expect(image.height, equals(400)); |
| |
| // Try building one with two child layers and make sure it renders them both. |
| boundary = RenderRepaintBoundary(); |
| final RenderStack stack = RenderStack()..alignment = Alignment.topLeft; |
| final RenderDecoratedBox blackBox = RenderDecoratedBox( |
| decoration: const BoxDecoration(color: Color(0xff000000)), |
| child: RenderConstrainedBox( |
| additionalConstraints: BoxConstraints.tight(const Size.square(20.0)), |
| )); |
| stack.add(RenderOpacity() |
| ..opacity = 0.5 |
| ..child = blackBox); |
| final RenderDecoratedBox whiteBox = RenderDecoratedBox( |
| decoration: const BoxDecoration(color: Color(0xffffffff)), |
| child: RenderConstrainedBox( |
| additionalConstraints: BoxConstraints.tight(const Size.square(10.0)), |
| )); |
| final RenderPositionedBox positioned = RenderPositionedBox( |
| widthFactor: 2.0, |
| heightFactor: 2.0, |
| alignment: Alignment.topRight, |
| child: whiteBox, |
| ); |
| stack.add(positioned); |
| boundary.child = stack; |
| layout(boundary, constraints: BoxConstraints.tight(const Size(20.0, 20.0))); |
| pumpFrame(phase: EnginePhase.composite); |
| image = await boundary.toImage(); |
| expect(image.width, equals(20)); |
| expect(image.height, equals(20)); |
| ByteData data = await image.toByteData(); |
| |
| int getPixel(int x, int y) => data.getUint32((x + y * image.width) * 4); |
| |
| expect(data.lengthInBytes, equals(20 * 20 * 4)); |
| expect(data.elementSizeInBytes, equals(1)); |
| expect(getPixel(0, 0), equals(0x00000080)); |
| expect(getPixel(image.width - 1, 0 ), equals(0xffffffff)); |
| |
| final OffsetLayer layer = boundary.debugLayer as OffsetLayer; |
| |
| image = await layer.toImage(Offset.zero & const Size(20.0, 20.0)); |
| expect(image.width, equals(20)); |
| expect(image.height, equals(20)); |
| data = await image.toByteData(); |
| expect(getPixel(0, 0), equals(0x00000080)); |
| expect(getPixel(image.width - 1, 0 ), equals(0xffffffff)); |
| |
| // non-zero offsets. |
| image = await layer.toImage(const Offset(-10.0, -10.0) & const Size(30.0, 30.0)); |
| expect(image.width, equals(30)); |
| expect(image.height, equals(30)); |
| data = await image.toByteData(); |
| expect(getPixel(0, 0), equals(0x00000000)); |
| expect(getPixel(10, 10), equals(0x00000080)); |
| expect(getPixel(image.width - 1, 0), equals(0x00000000)); |
| expect(getPixel(image.width - 1, 10), equals(0xffffffff)); |
| |
| // offset combined with a custom pixel ratio. |
| image = await layer.toImage(const Offset(-10.0, -10.0) & const Size(30.0, 30.0), pixelRatio: 2.0); |
| expect(image.width, equals(60)); |
| expect(image.height, equals(60)); |
| data = await image.toByteData(); |
| expect(getPixel(0, 0), equals(0x00000000)); |
| expect(getPixel(20, 20), equals(0x00000080)); |
| expect(getPixel(image.width - 1, 0), equals(0x00000000)); |
| expect(getPixel(image.width - 1, 20), equals(0xffffffff)); |
| }, skip: isBrowser); |
| |
| test('RenderOpacity does not composite if it is transparent', () { |
| final RenderOpacity renderOpacity = RenderOpacity( |
| opacity: 0.0, |
| child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter |
| ); |
| |
| layout(renderOpacity, phase: EnginePhase.composite); |
| expect(renderOpacity.needsCompositing, false); |
| }); |
| |
| test('RenderOpacity does not composite if it is opaque', () { |
| final RenderOpacity renderOpacity = RenderOpacity( |
| opacity: 1.0, |
| child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter |
| ); |
| |
| layout(renderOpacity, phase: EnginePhase.composite); |
| expect(renderOpacity.needsCompositing, false); |
| }); |
| |
| test('RenderOpacity reuses its layer', () { |
| _testLayerReuse<OpacityLayer>(RenderOpacity( |
| opacity: 0.5, // must not be 0 or 1.0. Otherwise, it won't create a layer |
| child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter |
| )); |
| }); |
| |
| test('RenderAnimatedOpacity does not composite if it is transparent', () async { |
| final Animation<double> opacityAnimation = AnimationController( |
| vsync: FakeTickerProvider(), |
| )..value = 0.0; |
| |
| final RenderAnimatedOpacity renderAnimatedOpacity = RenderAnimatedOpacity( |
| alwaysIncludeSemantics: false, |
| opacity: opacityAnimation, |
| child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter |
| ); |
| |
| layout(renderAnimatedOpacity, phase: EnginePhase.composite); |
| expect(renderAnimatedOpacity.needsCompositing, false); |
| }); |
| |
| test('RenderAnimatedOpacity does not composite if it is opaque', () { |
| final Animation<double> opacityAnimation = AnimationController( |
| vsync: FakeTickerProvider(), |
| )..value = 1.0; |
| |
| final RenderAnimatedOpacity renderAnimatedOpacity = RenderAnimatedOpacity( |
| alwaysIncludeSemantics: false, |
| opacity: opacityAnimation, |
| child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter |
| ); |
| |
| layout(renderAnimatedOpacity, phase: EnginePhase.composite); |
| expect(renderAnimatedOpacity.needsCompositing, false); |
| }); |
| |
| test('RenderAnimatedOpacity reuses its layer', () { |
| final Animation<double> opacityAnimation = AnimationController( |
| vsync: FakeTickerProvider(), |
| )..value = 0.5; // must not be 0 or 1.0. Otherwise, it won't create a layer |
| |
| _testLayerReuse<OpacityLayer>(RenderAnimatedOpacity( |
| opacity: opacityAnimation, |
| child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter |
| )); |
| }); |
| |
| test('RenderShaderMask reuses its layer', () { |
| _testLayerReuse<ShaderMaskLayer>(RenderShaderMask( |
| shaderCallback: (Rect rect) { |
| return ui.Gradient.radial( |
| rect.center, |
| rect.shortestSide / 2.0, |
| const <Color>[Color.fromRGBO(0, 0, 0, 1.0), Color.fromRGBO(255, 255, 255, 1.0)], |
| ); |
| }, |
| child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter |
| )); |
| }); |
| |
| test('RenderBackdropFilter reuses its layer', () { |
| _testLayerReuse<BackdropFilterLayer>(RenderBackdropFilter( |
| filter: ui.ImageFilter.blur(), |
| child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter |
| )); |
| }); |
| |
| test('RenderClipRect reuses its layer', () { |
| _testLayerReuse<ClipRectLayer>(RenderClipRect( |
| clipper: _TestRectClipper(), |
| // Inject opacity under the clip to force compositing. |
| child: RenderOpacity( |
| opacity: 0.5, |
| child: RenderSizedBox(const Size(1.0, 1.0)), |
| ), // size doesn't matter |
| )); |
| }); |
| |
| test('RenderClipRRect reuses its layer', () { |
| _testLayerReuse<ClipRRectLayer>(RenderClipRRect( |
| clipper: _TestRRectClipper(), |
| // Inject opacity under the clip to force compositing. |
| child: RenderOpacity( |
| opacity: 0.5, |
| child: RenderSizedBox(const Size(1.0, 1.0)), |
| ), // size doesn't matter |
| )); |
| }); |
| |
| test('RenderClipOval reuses its layer', () { |
| _testLayerReuse<ClipPathLayer>(RenderClipOval( |
| clipper: _TestRectClipper(), |
| // Inject opacity under the clip to force compositing. |
| child: RenderOpacity( |
| opacity: 0.5, |
| child: RenderSizedBox(const Size(1.0, 1.0)), |
| ), // size doesn't matter |
| )); |
| }); |
| |
| test('RenderClipPath reuses its layer', () { |
| _testLayerReuse<ClipPathLayer>(RenderClipPath( |
| clipper: _TestPathClipper(), |
| // Inject opacity under the clip to force compositing. |
| child: RenderOpacity( |
| opacity: 0.5, |
| child: RenderSizedBox(const Size(1.0, 1.0)), |
| ), // size doesn't matter |
| )); |
| }); |
| |
| test('RenderPhysicalModel reuses its layer', () { |
| _testLayerReuse<PhysicalModelLayer>(RenderPhysicalModel( |
| color: const Color.fromRGBO(0, 0, 0, 1.0), |
| // Inject opacity under the clip to force compositing. |
| child: RenderOpacity( |
| opacity: 0.5, |
| child: RenderSizedBox(const Size(1.0, 1.0)), |
| ), // size doesn't matter |
| )); |
| }); |
| |
| test('RenderPhysicalShape reuses its layer', () { |
| _testLayerReuse<PhysicalModelLayer>(RenderPhysicalShape( |
| clipper: _TestPathClipper(), |
| color: const Color.fromRGBO(0, 0, 0, 1.0), |
| // Inject opacity under the clip to force compositing. |
| child: RenderOpacity( |
| opacity: 0.5, |
| child: RenderSizedBox(const Size(1.0, 1.0)), |
| ), // size doesn't matter |
| )); |
| }); |
| |
| test('RenderTransform reuses its layer', () { |
| _testLayerReuse<TransformLayer>(RenderTransform( |
| // Use a 3D transform to force compositing. |
| transform: Matrix4.rotationX(0.1), |
| // Inject opacity under the clip to force compositing. |
| child: RenderOpacity( |
| opacity: 0.5, |
| child: RenderSizedBox(const Size(1.0, 1.0)), |
| ), // size doesn't matter |
| )); |
| }); |
| |
| void _testFittedBoxWithClipRectLayer() { |
| _testLayerReuse<ClipRectLayer>(RenderFittedBox( |
| alignment: Alignment.center, |
| fit: BoxFit.cover, |
| // Inject opacity under the clip to force compositing. |
| child: RenderOpacity( |
| opacity: 0.5, |
| child: RenderSizedBox(const Size(100.0, 200.0)), |
| ), // size doesn't matter |
| )); |
| } |
| |
| void _testFittedBoxWithTransformLayer() { |
| _testLayerReuse<TransformLayer>(RenderFittedBox( |
| alignment: Alignment.center, |
| fit: BoxFit.fill, |
| // Inject opacity under the clip to force compositing. |
| child: RenderOpacity( |
| opacity: 0.5, |
| child: RenderSizedBox(const Size(1, 1)), |
| ), // size doesn't matter |
| )); |
| } |
| |
| test('RenderFittedBox reuses ClipRectLayer', () { |
| _testFittedBoxWithClipRectLayer(); |
| }); |
| |
| test('RenderFittedBox reuses TransformLayer', () { |
| _testFittedBoxWithTransformLayer(); |
| }); |
| |
| test('RenderFittedBox switches between ClipRectLayer and TransformLayer, and reuses them', () { |
| _testFittedBoxWithClipRectLayer(); |
| |
| // clip -> transform |
| _testFittedBoxWithTransformLayer(); |
| // transform -> clip |
| _testFittedBoxWithClipRectLayer(); |
| }); |
| |
| test('RenderMouseRegion can change properties when detached', () { |
| renderer.initMouseTracker(MouseTracker( |
| renderer.pointerRouter, |
| (_) => <MouseTrackerAnnotation>[], |
| )); |
| final RenderMouseRegion object = RenderMouseRegion(); |
| object |
| ..opaque = false |
| ..onEnter = (_) {} |
| ..onExit = (_) {} |
| ..onHover = (_) {}; |
| // Passes if no error is thrown |
| }); |
| |
| test('RenderFractionalTranslation updates its semantics after its translation value is set', () { |
| final _TestSemanticsUpdateRenderFractionalTranslation box = _TestSemanticsUpdateRenderFractionalTranslation( |
| translation: const Offset(0.5, 0.5), |
| ); |
| layout(box, constraints: BoxConstraints.tight(const Size(200.0, 200.0))); |
| expect(box.markNeedsSemanticsUpdateCallCount, 1); |
| box.translation = const Offset(0.4, 0.4); |
| expect(box.markNeedsSemanticsUpdateCallCount, 2); |
| box.translation = const Offset(0.3, 0.3); |
| expect(box.markNeedsSemanticsUpdateCallCount, 3); |
| }); |
| |
| test('RenderFollowerLayer hit test without a leader layer and the showWhenUnlinked is true', () { |
| final RenderFollowerLayer follower = RenderFollowerLayer( |
| link: LayerLink(), |
| showWhenUnlinked: true, |
| child: RenderSizedBox(const Size(1.0, 1.0)), |
| ); |
| layout(follower, constraints: BoxConstraints.tight(const Size(200.0, 200.0))); |
| final BoxHitTestResult hitTestResult = BoxHitTestResult(); |
| expect(follower.hitTest(hitTestResult, position: const Offset(0.0, 0.0)), isTrue); |
| }); |
| |
| test('RenderFollowerLayer hit test without a leader layer and the showWhenUnlinked is false', () { |
| final RenderFollowerLayer follower = RenderFollowerLayer( |
| link: LayerLink(), |
| showWhenUnlinked: false, |
| child: RenderSizedBox(const Size(1.0, 1.0)), |
| ); |
| layout(follower, constraints: BoxConstraints.tight(const Size(200.0, 200.0))); |
| final BoxHitTestResult hitTestResult = BoxHitTestResult(); |
| expect(follower.hitTest(hitTestResult, position: const Offset(0.0, 0.0)), isFalse); |
| }); |
| |
| test('RenderFollowerLayer hit test with a leader layer and the showWhenUnlinked is true', () { |
| // Creates a layer link with a leader. |
| final LayerLink link = LayerLink(); |
| final LeaderLayer leader = LeaderLayer(link: link); |
| leader.attach(Object()); |
| |
| final RenderFollowerLayer follower = RenderFollowerLayer( |
| link: link, |
| showWhenUnlinked: true, |
| child: RenderSizedBox(const Size(1.0, 1.0)), |
| ); |
| layout(follower, constraints: BoxConstraints.tight(const Size(200.0, 200.0))); |
| final BoxHitTestResult hitTestResult = BoxHitTestResult(); |
| expect(follower.hitTest(hitTestResult, position: const Offset(0.0, 0.0)), isTrue); |
| }); |
| |
| test('RenderFollowerLayer hit test with a leader layer and the showWhenUnlinked is false', () { |
| // Creates a layer link with a leader. |
| final LayerLink link = LayerLink(); |
| final LeaderLayer leader = LeaderLayer(link: link); |
| leader.attach(Object()); |
| |
| final RenderFollowerLayer follower = RenderFollowerLayer( |
| link: link, |
| showWhenUnlinked: false, |
| child: RenderSizedBox(const Size(1.0, 1.0)), |
| ); |
| layout(follower, constraints: BoxConstraints.tight(const Size(200.0, 200.0))); |
| final BoxHitTestResult hitTestResult = BoxHitTestResult(); |
| // The follower is still hit testable because there is a leader layer. |
| expect(follower.hitTest(hitTestResult, position: const Offset(0.0, 0.0)), isTrue); |
| }); |
| } |
| |
| class _TestRectClipper extends CustomClipper<Rect> { |
| @override |
| Rect getClip(Size size) { |
| return Rect.zero; |
| } |
| |
| @override |
| Rect getApproximateClipRect(Size size) => getClip(size); |
| |
| @override |
| bool shouldReclip(_TestRectClipper oldClipper) => true; |
| } |
| |
| class _TestRRectClipper extends CustomClipper<RRect> { |
| @override |
| RRect getClip(Size size) { |
| return RRect.zero; |
| } |
| |
| @override |
| Rect getApproximateClipRect(Size size) => getClip(size).outerRect; |
| |
| @override |
| bool shouldReclip(_TestRRectClipper oldClipper) => true; |
| } |
| |
| // Forces two frames and checks that: |
| // - a layer is created on the first frame |
| // - the layer is reused on the second frame |
| void _testLayerReuse<L extends Layer>(RenderBox renderObject) { |
| expect(L, isNot(Layer)); |
| expect(renderObject.debugLayer, null); |
| layout(renderObject, phase: EnginePhase.paint, constraints: BoxConstraints.tight(const Size(10, 10))); |
| final Layer layer = renderObject.debugLayer; |
| expect(layer, isA<L>()); |
| expect(layer, isNotNull); |
| |
| // Mark for repaint otherwise pumpFrame is a noop. |
| renderObject.markNeedsPaint(); |
| expect(renderObject.debugNeedsPaint, true); |
| pumpFrame(phase: EnginePhase.paint); |
| expect(renderObject.debugNeedsPaint, false); |
| expect(renderObject.debugLayer, same(layer)); |
| } |
| |
| class _TestPathClipper extends CustomClipper<Path> { |
| @override |
| Path getClip(Size size) { |
| return Path() |
| ..addRect(const Rect.fromLTWH(50.0, 50.0, 100.0, 100.0)); |
| } |
| @override |
| bool shouldReclip(_TestPathClipper oldClipper) => false; |
| } |
| |
| class _TestSemanticsUpdateRenderFractionalTranslation extends RenderFractionalTranslation { |
| _TestSemanticsUpdateRenderFractionalTranslation({ |
| @required Offset translation, |
| RenderBox child, |
| }) : super(translation: translation, child: child); |
| |
| int markNeedsSemanticsUpdateCallCount = 0; |
| |
| @override |
| void markNeedsSemanticsUpdate() { |
| markNeedsSemanticsUpdateCallCount++; |
| super.markNeedsSemanticsUpdate(); |
| } |
| } |