Created
March 6, 2026 20:21
-
-
Save Piinks/0b5be8ab9ac39ef331106351b44d8494 to your computer and use it in GitHub Desktop.
sliver clip tests
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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 'package:flutter/rendering.dart'; | |
| import 'package:flutter/widgets.dart'; | |
| import 'package:flutter_test/flutter_test.dart'; | |
| void main() { | |
| testWidgets('SliverClipRRect uses correctheight for clip calculation', (WidgetTester tester) async { | |
| final ScrollController controller = ScrollController(); | |
| await tester.pumpWidget( | |
| WidgetsApp( | |
| color: const Color(0xffffffff), | |
| onGenerateRoute: (settings) => PageRouteBuilder( | |
| pageBuilder: (_, _, _) => CustomScrollView( | |
| controller: controller, | |
| slivers: <Widget>[ | |
| const SliverPersistentHeader( | |
| delegate: _SliverPersistentHeaderDelegate(100), // 100px Pinned Header | |
| pinned: true, | |
| ), | |
| SliverClipRRect( | |
| borderRadius: const BorderRadius.all(Radius.circular(40)), | |
| sliver: SliverToBoxAdapter( | |
| child: Container( | |
| height: 100, // Total Height: 100px. middleRect.height is 20px (100 - 40 - 40). | |
| color: const Color(0xFF2196F3), | |
| ), | |
| ), | |
| ), | |
| const SliverToBoxAdapter(child: SizedBox(height: 1000)), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| // Scroll by 50px. | |
| // The header covers 0-100px in the viewport. | |
| // The SliverClipRRect starts at 50px in the viewport (100px header - 50px scroll). | |
| // The overlap is 50px. The clip should start at local y=50. | |
| controller.jumpTo(50); | |
| await tester.pump(); | |
| final RenderSliverClipRRect renderSliver = tester.renderObject(find.byType(SliverClipRRect).first); | |
| final RRect clip = renderSliver.getClip()!; | |
| // ACTUAL: clip.top was 20.0 instead of 50.0. | |
| // REASON: It used middleRect.height (20) instead of total height (100). | |
| expect( | |
| clip.top, | |
| 50.0, | |
| reason: 'clip.top should be 50.0 to cover the overlap. Using middleRect.height (20) results in a miscalculated clip origin.' | |
| ); | |
| }); | |
| testWidgets('SliverClipRRect rounded overlap cut causes content leak', (WidgetTester tester) async { | |
| final ScrollController controller = ScrollController(); | |
| await tester.pumpWidget( | |
| WidgetsApp( | |
| color: const Color(0xffffffff), | |
| onGenerateRoute: (settings) => PageRouteBuilder( | |
| pageBuilder: (_, _, _) => CustomScrollView( | |
| controller: controller, | |
| slivers: <Widget>[ | |
| const SliverPersistentHeader( | |
| delegate: _SliverPersistentHeaderDelegate(100), | |
| pinned: true, | |
| ), | |
| SliverClipRRect( | |
| borderRadius: const BorderRadius.all(Radius.circular(40)), | |
| sliver: SliverToBoxAdapter( | |
| child: Container( | |
| height: 100, | |
| color: const Color(0xFF2196F3), | |
| ), | |
| ), | |
| ), | |
| const SliverToBoxAdapter(child: SizedBox(height: 1000)), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| controller.jumpTo(20); | |
| await tester.pump(); | |
| final RenderSliverClipRRect renderSliver = tester.renderObject(find.byType(SliverClipRRect).first); | |
| // Test point (1, 21) in local coordinates. | |
| // If the cut is rounded, this point is clipped (hit=false). | |
| // This confirms that content is visible (or hidden) following a curve, | |
| // which leaves "gaps" against a straight pinned header. | |
| final SliverHitTestResult result = SliverHitTestResult(); | |
| final bool hit = renderSliver.hitTest(result, mainAxisPosition: 21, crossAxisPosition: 1); | |
| expect( | |
| hit, | |
| isTrue, | |
| reason: 'Content at (1, 21) is clipped because the overlap cut is rounded, leaving visual gaps under the header.' | |
| ); | |
| }); | |
| testWidgets('SliverClipRRect reverse scroll overlap calculation', (WidgetTester tester) async { | |
| final ScrollController controller = ScrollController(); | |
| await tester.pumpWidget( | |
| WidgetsApp( | |
| color: const Color(0xffffffff), | |
| onGenerateRoute: (settings) => PageRouteBuilder( | |
| pageBuilder: (_, _, _) => CustomScrollView( | |
| reverse: true, // REVERSE SCROLLING | |
| controller: controller, | |
| slivers: <Widget>[ | |
| const SliverToBoxAdapter(child: SizedBox(height: 1000)), | |
| SliverClipRRect( | |
| borderRadius: const BorderRadius.all(Radius.circular(40)), | |
| sliver: SliverToBoxAdapter( | |
| child: Container( | |
| height: 100, | |
| color: const Color(0xFF2196F3), | |
| ), | |
| ), | |
| ), | |
| const SliverPersistentHeader( | |
| delegate: _SliverPersistentHeaderDelegate(100), // Pinned Header at the BOTTOM | |
| pinned: true, | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| // Scroll so there is 50px overlap at the bottom. | |
| controller.jumpTo(50); | |
| await tester.pump(); | |
| final RenderSliverClipRRect renderSliver = tester.renderObject(find.byType(SliverClipRRect).first); | |
| expect(renderSliver.constraints.overlap, 50.0); | |
| final RRect clip = renderSliver.getClip()!; | |
| // For AxisDirection.up, it uses newClip.copyWith(bottom: geometry!.paintExtent - clipOrigin) | |
| // If clipOrigin is wrong (20 instead of 50), the bottom clip will be misplaced. | |
| expect( | |
| clip.bottom, | |
| 50.0, | |
| reason: 'Reverse scroll overlap clipping is incorrect.' | |
| ); | |
| }); | |
| } | |
| class _SliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate { | |
| const _SliverPersistentHeaderDelegate(this.height); | |
| final double height; | |
| @override | |
| Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) => SizedBox(height: height); | |
| @override | |
| double get maxExtent => height; | |
| @override | |
| double get minExtent => height; | |
| @override | |
| bool shouldRebuild(covariant _SliverPersistentHeaderDelegate oldDelegate) => height != oldDelegate.height; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment