// 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/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('DecoratedSliver creates, paints, and disposes BoxPainter', (WidgetTester tester) async { final TestDecoration decoration = TestDecoration(); await tester.pumpWidget(MaterialApp( home: Scaffold( body: CustomScrollView( slivers: <Widget>[ DecoratedSliver( decoration: decoration, sliver: const SliverToBoxAdapter( child: SizedBox(width: 100, height: 100), ), ) ], ) ) )); expect(decoration.painters, hasLength(1)); expect(decoration.painters.last.lastConfiguration!.size, const Size(800, 100)); expect(decoration.painters.last.lastOffset, Offset.zero); expect(decoration.painters.last.disposed, false); await tester.pumpWidget(const SizedBox()); expect(decoration.painters, hasLength(1)); expect(decoration.painters.last.disposed, true); }); testWidgets('DecoratedSliver can update box painter', (WidgetTester tester) async { final TestDecoration decorationA = TestDecoration(); final TestDecoration decorationB = TestDecoration(); Decoration activateDecoration = decorationA; late StateSetter localSetState; await tester.pumpWidget(MaterialApp( home: Scaffold( body: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { localSetState = setState; return CustomScrollView( slivers: <Widget>[ DecoratedSliver( decoration: activateDecoration, sliver: const SliverToBoxAdapter( child: SizedBox(width: 100, height: 100), ), ) ], ); }, ) ) )); expect(decorationA.painters, hasLength(1)); expect(decorationA.painters.last.paintCount, 1); expect(decorationB.painters, hasLength(0)); localSetState(() { activateDecoration = decorationB; }); await tester.pump(); expect(decorationA.painters, hasLength(1)); expect(decorationB.painters, hasLength(1)); expect(decorationB.painters.last.paintCount, 1); }); testWidgets('DecoratedSliver can update DecorationPosition', (WidgetTester tester) async { final TestDecoration decoration = TestDecoration(); DecorationPosition activePosition = DecorationPosition.foreground; late StateSetter localSetState; await tester.pumpWidget(MaterialApp( home: Scaffold( body: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { localSetState = setState; return CustomScrollView( slivers: <Widget>[ DecoratedSliver( decoration: decoration, position: activePosition, sliver: const SliverToBoxAdapter( child: SizedBox(width: 100, height: 100), ), ) ], ); }, ) ) )); expect(decoration.painters, hasLength(1)); expect(decoration.painters.last.paintCount, 1); localSetState(() { activePosition = DecorationPosition.background; }); await tester.pump(); expect(decoration.painters, hasLength(1)); expect(decoration.painters.last.paintCount, 2); }); testWidgets('DecoratedSliver golden test', (WidgetTester tester) async { const BoxDecoration decoration = BoxDecoration( color: Colors.blue, ); final Key backgroundKey = UniqueKey(); await tester.pumpWidget(MaterialApp( home: Scaffold( body: RepaintBoundary( key: backgroundKey, child: CustomScrollView( slivers: <Widget>[ DecoratedSliver( decoration: decoration, sliver: SliverPadding( padding: const EdgeInsets.all(16), sliver: SliverList( delegate: SliverChildListDelegate.fixed(<Widget>[ Container( height: 100, color: Colors.red, ), Container( height: 100, color: Colors.yellow, ), Container( height: 100, color: Colors.red, ), ]), ), ), ), ], ), ), ) )); await expectLater(find.byKey(backgroundKey), matchesGoldenFile('decorated_sliver.moon.background.png')); final Key foregroundKey = UniqueKey(); await tester.pumpWidget(MaterialApp( home: Scaffold( body: RepaintBoundary( key: foregroundKey, child: CustomScrollView( slivers: <Widget>[ DecoratedSliver( decoration: decoration, position: DecorationPosition.foreground, sliver: SliverPadding( padding: const EdgeInsets.all(16), sliver: SliverList( delegate: SliverChildListDelegate.fixed(<Widget>[ Container( height: 100, color: Colors.red, ), Container( height: 100, color: Colors.yellow, ), Container( height: 100, color: Colors.red, ), ]), ), ), ), ], ), ), ) )); await expectLater(find.byKey(foregroundKey), matchesGoldenFile('decorated_sliver.moon.foreground.png')); }); testWidgets('DecoratedSliver paints its border correctly vertically', (WidgetTester tester) async { const Key key = Key('DecoratedSliver with border'); const Color black = Color(0xFF000000); final ScrollController controller = ScrollController(); addTearDown(controller.dispose); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: Align( alignment: Alignment.topLeft, child: SizedBox( height: 300, width: 100, child: CustomScrollView( controller: controller, slivers: <Widget>[ DecoratedSliver( key: key, decoration: BoxDecoration(border: Border.all()), sliver: const SliverToBoxAdapter( child: SizedBox(width: 100, height: 500), ), ), ], ), ), ), )); controller.jumpTo(200); await tester.pumpAndSettle(); expect(find.byKey(key), paints..rect( rect: const Offset(0.5, -199.5) & const Size(99, 499), color: black, style: PaintingStyle.stroke, strokeWidth: 1.0, )); }); testWidgets('DecoratedSliver paints its border correctly vertically reverse', (WidgetTester tester) async { const Key key = Key('DecoratedSliver with border'); const Color black = Color(0xFF000000); final ScrollController controller = ScrollController(); addTearDown(controller.dispose); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: Align( alignment: Alignment.topLeft, child: SizedBox( height: 300, width: 100, child: CustomScrollView( controller: controller, reverse: true, slivers: <Widget>[ DecoratedSliver( key: key, decoration: BoxDecoration(border: Border.all()), sliver: const SliverToBoxAdapter( child: SizedBox(width: 100, height: 500), ), ), ], ), ), ), )); controller.jumpTo(200); await tester.pumpAndSettle(); expect(find.byKey(key), paints..rect( rect: const Offset(0.5, -199.5) & const Size(99, 499), color: black, style: PaintingStyle.stroke, strokeWidth: 1.0, )); }); testWidgets('DecoratedSliver paints its border correctly horizontally', (WidgetTester tester) async { const Key key = Key('DecoratedSliver with border'); const Color black = Color(0xFF000000); final ScrollController controller = ScrollController(); addTearDown(controller.dispose); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: Align( alignment: Alignment.topLeft, child: SizedBox( height: 100, width: 300, child: CustomScrollView( scrollDirection: Axis.horizontal, controller: controller, slivers: <Widget>[ DecoratedSliver( key: key, decoration: BoxDecoration(border: Border.all()), sliver: const SliverToBoxAdapter( child: SizedBox(width: 500, height: 100), ), ), ], ), ), ), )); controller.jumpTo(200); await tester.pumpAndSettle(); expect(find.byKey(key), paints..rect( rect: const Offset(-199.5, 0.5) & const Size(499, 99), color: black, style: PaintingStyle.stroke, strokeWidth: 1.0, )); }); testWidgets('DecoratedSliver paints its border correctly horizontally reverse', (WidgetTester tester) async { const Key key = Key('DecoratedSliver with border'); const Color black = Color(0xFF000000); final ScrollController controller = ScrollController(); addTearDown(controller.dispose); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: Align( alignment: Alignment.topLeft, child: SizedBox( height: 100, width: 300, child: CustomScrollView( scrollDirection: Axis.horizontal, reverse: true, controller: controller, slivers: <Widget>[ DecoratedSliver( key: key, decoration: BoxDecoration(border: Border.all()), sliver: const SliverToBoxAdapter( child: SizedBox(width: 500, height: 100), ), ), ], ), ), ), )); controller.jumpTo(200); await tester.pumpAndSettle(); expect(find.byKey(key), paints..rect( rect: const Offset(-199.5, 0.5) & const Size(499, 99), color: black, style: PaintingStyle.stroke, strokeWidth: 1.0, )); }); testWidgets('DecoratedSliver works with SliverMainAxisGroup', (WidgetTester tester) async { const Key key = Key('DecoratedSliver with border'); const Color black = Color(0xFF000000); final ScrollController controller = ScrollController(); addTearDown(controller.dispose); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: Align( alignment: Alignment.topLeft, child: SizedBox( height: 100, width: 300, child: CustomScrollView( controller: controller, slivers: <Widget>[ DecoratedSliver( key: key, decoration: BoxDecoration(border: Border.all()), sliver: const SliverMainAxisGroup( slivers: <Widget>[ SliverToBoxAdapter(child: SizedBox(height: 100)), SliverToBoxAdapter(child: SizedBox(height: 100)), ], ), ), ], ), ), ), )); await tester.pumpAndSettle(); expect(find.byKey(key), paints..rect( rect: const Offset(0.5, 0.5) & const Size(299, 199), color: black, style: PaintingStyle.stroke, strokeWidth: 1.0, )); }); testWidgets('DecoratedSliver works with SliverCrossAxisGroup', (WidgetTester tester) async { const Key key = Key('DecoratedSliver with border'); const Color black = Color(0xFF000000); final ScrollController controller = ScrollController(); addTearDown(controller.dispose); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: Align( alignment: Alignment.topLeft, child: SizedBox( height: 100, width: 300, child: CustomScrollView( controller: controller, slivers: <Widget>[ DecoratedSliver( key: key, decoration: BoxDecoration(border: Border.all()), sliver: const SliverCrossAxisGroup( slivers: <Widget>[ SliverToBoxAdapter(child: SizedBox(height: 100)), SliverToBoxAdapter(child: SizedBox(height: 100)), ], ), ), ], ), ), ), )); await tester.pumpAndSettle(); expect(find.byKey(key), paints..rect( rect: const Offset(0.5, 0.5) & const Size(299, 99), color: black, style: PaintingStyle.stroke, strokeWidth: 1.0, )); }); testWidgets('DecoratedSliver draws only up to the bottom cache when sliver has infinite scroll extent', (WidgetTester tester) async { const Key key = Key('DecoratedSliver with border'); const Color black = Color(0xFF000000); final ScrollController controller = ScrollController(); addTearDown(controller.dispose); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: Align( alignment: Alignment.topLeft, child: SizedBox( height: 100, width: 300, child: CustomScrollView( controller: controller, slivers: <Widget>[ DecoratedSliver( key: key, decoration: BoxDecoration(border: Border.all()), sliver: SliverList.builder( itemBuilder: (BuildContext context, int index) => const SizedBox(height: 100), ), ), ], ), ), ), )); expect(find.byKey(key), paints..rect( rect: const Offset(0.5, 0.5) & const Size(299, 349), color: black, style: PaintingStyle.stroke, strokeWidth: 1.0, )); controller.jumpTo(200); await tester.pumpAndSettle(); // Note that the bottom edge is of the rect is the same as above. expect(find.byKey(key), paints..rect( rect: const Offset(0.5, -199.5) & const Size(299, 549), color: black, style: PaintingStyle.stroke, strokeWidth: 1.0, )); }); } class TestDecoration extends Decoration { final List<TestBoxPainter> painters = <TestBoxPainter>[]; @override BoxPainter createBoxPainter([VoidCallback? onChanged]) { final TestBoxPainter painter = TestBoxPainter(); painters.add(painter); return painter; } } class TestBoxPainter extends BoxPainter { Offset? lastOffset; ImageConfiguration? lastConfiguration; bool disposed = false; int paintCount = 0; @override void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { lastOffset = offset; lastConfiguration = configuration; paintCount += 1; } @override void dispose() { assert(!disposed); disposed = true; super.dispose(); } }