Skip to content

Commit 49eff8b

Browse files
committed
sticky_header test: Use CustomPaintOrderScrollView in test, example, and doc
By letting us control the paint order between slivers, this fixes all the skipped tests in this test file.
1 parent 5ab54d8 commit 49eff8b

File tree

3 files changed

+36
-40
lines changed

3 files changed

+36
-40
lines changed

lib/example/sticky_header.dart

+6-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
/// so as to set the app ID differently.
1818
library;
1919

20-
import 'package:flutter/material.dart';
20+
// ignore: undefined_hidden_name // anticipates https://github.com/flutter/flutter/pull/164818
21+
import 'package:flutter/material.dart' hide SliverPaintOrder;
2122

23+
import '../widgets/scrolling.dart';
2224
import '../widgets/sticky_header.dart';
2325

2426
/// Example page using [StickyHeaderListView] and [StickyHeaderItem] in a
@@ -151,9 +153,11 @@ class ExampleVerticalDouble extends StatelessWidget {
151153

152154
return Scaffold(
153155
appBar: AppBar(title: Text(title)),
154-
body: CustomScrollView(
156+
body: CustomPaintOrderScrollView(
155157
semanticChildCount: numSections,
156158
center: centerKey,
159+
paintOrder: headerAtBottom ?
160+
SliverPaintOrder.lastIsTop : SliverPaintOrder.firstIsTop,
157161
slivers: [
158162
SliverStickyHeaderList(
159163
key: const ValueKey('top'),

lib/widgets/sticky_header.dart

+7-6
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,8 @@ enum _HeaderGrowthPlacement {
318318
/// When the list item that controls the sticky header has
319319
/// [StickyHeaderItem.allowOverflow] true, the header will be permitted
320320
/// to overflow not only the item but this whole sliver.
321+
/// (This provides seamless behavior if, for example, two back-to-back slivers
322+
/// are used for implementing a double-ended scrollable list.)
321323
///
322324
/// The caller is responsible for arranging the paint order between slivers
323325
/// so that this works correctly: a sliver that might overflow must be painted
@@ -327,12 +329,11 @@ enum _HeaderGrowthPlacement {
327329
/// then this [SliverStickyHeaderList] must paint after any slivers that appear
328330
/// to the right of this sliver.
329331
///
330-
/// At present there's no off-the-shelf way to fully control the paint order
331-
/// between slivers.
332-
/// See the implementation of [RenderViewport.childrenInPaintOrder] for the
333-
/// paint order provided by [CustomScrollView]; it meets the above needs
334-
/// for some arrangements of slivers and values of [headerPlacement],
335-
/// but not others.
332+
/// To control the viewport's paint order,
333+
/// consider using [CustomPaintOrderScrollView] instead of [CustomScrollView].
334+
/// Then [SliverPaintOrder.firstIsTop] for [HeaderPlacement.scrollingStart],
335+
/// or [SliverPaintOrder.lastIsTop] for [HeaderPlacement.scrollingEnd],
336+
/// suffices for meeting the needs above.
336337
class SliverStickyHeaderList extends RenderObjectWidget {
337338
SliverStickyHeaderList({
338339
super.key,

test/widgets/sticky_header_test.dart

+23-32
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ import 'package:checks/checks.dart';
44
import 'package:collection/collection.dart';
55
import 'package:flutter/foundation.dart';
66
import 'package:flutter/gestures.dart';
7-
import 'package:flutter/rendering.dart';
8-
import 'package:flutter/widgets.dart';
7+
// ignore: undefined_hidden_name // anticipates https://github.com/flutter/flutter/pull/164818
8+
import 'package:flutter/rendering.dart' hide SliverPaintOrder;
9+
// ignore: undefined_hidden_name // anticipates https://github.com/flutter/flutter/pull/164818
10+
import 'package:flutter/widgets.dart' hide SliverPaintOrder;
911
import 'package:flutter_test/flutter_test.dart';
12+
import 'package:zulip/widgets/scrolling.dart';
1013
import 'package:zulip/widgets/sticky_header.dart';
1114

1215
void main() {
@@ -230,19 +233,24 @@ void main() {
230233
});
231234

232235
testWidgets('hit-testing for header overflowing sliver', (tester) async {
236+
const centerKey = ValueKey('center');
233237
final controller = ScrollController();
234238
await tester.pumpWidget(Directionality(textDirection: TextDirection.ltr,
235-
child: CustomScrollView(
239+
child: CustomPaintOrderScrollView(
236240
controller: controller,
241+
anchor: 0.0,
242+
center: centerKey,
243+
paintOrder: SliverPaintOrder.firstIsTop,
237244
slivers: [
238245
SliverStickyHeaderList(
239246
headerPlacement: HeaderPlacement.scrollingStart,
240247
delegate: SliverChildListDelegate(
241248
List.generate(100, (i) => StickyHeaderItem(
242249
allowOverflow: true,
243-
header: _Header(i, height: 20),
244-
child: _Item(i, height: 100))))),
250+
header: _Header(99 - i, height: 20),
251+
child: _Item(99 - i, height: 100))))),
245252
SliverStickyHeaderList(
253+
key: centerKey,
246254
headerPlacement: HeaderPlacement.scrollingStart,
247255
delegate: SliverChildListDelegate(
248256
List.generate(100, (i) => StickyHeaderItem(
@@ -251,9 +259,8 @@ void main() {
251259
child: _Item(100 + i, height: 100))))),
252260
])));
253261

254-
const topExtent = 100 * 100;
255262
for (double topHeight in [5, 10, 15, 20]) {
256-
controller.jumpTo(topExtent - topHeight);
263+
controller.jumpTo(-topHeight);
257264
await tester.pump();
258265
// The top sliver occupies height [topHeight].
259266
// Its header overhangs by `20 - topHeight`.
@@ -327,49 +334,34 @@ Future<void> _checkSequence(
327334
];
328335

329336
final double anchor;
330-
bool paintOrderGood;
331337
if (reverseGrowth) {
332338
slivers.reverseRange(0, slivers.length);
333339
anchor = 1.0;
334-
paintOrderGood = switch (sliverConfig) {
335-
_SliverConfig.single => true,
336-
// The last sliver will paint last.
337-
_SliverConfig.backToBack => headerPlacement == HeaderPlacement.scrollingEnd,
338-
// The last sliver will paint last.
339-
_SliverConfig.followed => headerPlacement == HeaderPlacement.scrollingEnd,
340-
};
341340
} else {
342341
anchor = 0.0;
343-
paintOrderGood = switch (sliverConfig) {
344-
_SliverConfig.single => true,
345-
// The last sliver will paint last.
346-
_SliverConfig.backToBack => headerPlacement == HeaderPlacement.scrollingEnd,
347-
// The first sliver will paint last.
348-
_SliverConfig.followed => headerPlacement == HeaderPlacement.scrollingStart,
349-
};
350342
}
351343

352-
final skipBecausePaintOrder = allowOverflow && !paintOrderGood;
353-
if (skipBecausePaintOrder) {
354-
// TODO need to control paint order of slivers within viewport in order to
355-
// make some configurations behave properly when headers overflow slivers
356-
markTestSkipped('sliver paint order');
357-
// Don't return yet; we'll still check layout, and skip specific affected checks below.
344+
SliverPaintOrder paintOrder = SliverPaintOrder.centerTopFirstBottom;
345+
if (!allowOverflow || (sliverConfig == _SliverConfig.single)) {
346+
// The paint order doesn't matter.
347+
} else {
348+
paintOrder = headerPlacement == HeaderPlacement.scrollingStart
349+
? SliverPaintOrder.firstIsTop : SliverPaintOrder.lastIsTop;
358350
}
359351

360-
361352
final controller = ScrollController();
362353
await tester.pumpWidget(Directionality(
363354
textDirection: textDirection ?? TextDirection.rtl,
364-
child: CustomScrollView(
355+
child: CustomPaintOrderScrollView(
365356
controller: controller,
366357
scrollDirection: axis,
367358
reverse: reverse,
368359
anchor: anchor,
369360
center: center,
361+
paintOrder: paintOrder,
370362
slivers: slivers)));
371363

372-
final overallSize = tester.getSize(find.byType(CustomScrollView));
364+
final overallSize = tester.getSize(find.bySubtype<CustomScrollView>());
373365
final extent = overallSize.onAxis(axis);
374366
assert(extent % 100 == 0);
375367
assert(sliverScrollExtent - extent > 100);
@@ -418,7 +410,6 @@ Future<void> _checkSequence(
418410
check(insetExtent(find.byType(_Header))).equals(expectedHeaderInsetExtent);
419411

420412
// Check the header gets hit when it should, and not when it shouldn't.
421-
if (skipBecausePaintOrder) return;
422413
await tester.tapAt(headerInset(1));
423414
await tester.tapAt(headerInset(expectedHeaderInsetExtent - 1));
424415
check(_TapLogged.takeTapLog())..length.equals(2)

0 commit comments

Comments
 (0)