blob: fc8fbf70a2155b73910105544123a0144b037b1e [file] [log] [blame]
// Copyright 2015 The Chromium 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_test/flutter_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'test_widgets.dart';
void main() {
testWidgets('ListView mount/dismount smoke test', (WidgetTester tester) async {
final List<int> callbackTracker = <int>[];
// the root view is 800x600 in the test environment
// so if our widget is 100 pixels tall, it should fit exactly 6 times.
Widget builder() {
return new Directionality(
textDirection: TextDirection.ltr,
child: new FlipWidget(
left: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
callbackTracker.add(index);
return new Container(
key: new ValueKey<int>(index),
height: 100.0,
child: new Text('$index'),
);
},
),
right: const Text('Not Today'),
),
);
}
await tester.pumpWidget(builder());
final FlipWidgetState testWidget = tester.state(find.byType(FlipWidget));
expect(callbackTracker, equals(<int>[
0, 1, 2, 3, 4, 5, // visible
6, 7, 8 // in cached area
]));
callbackTracker.clear();
testWidget.flip();
await tester.pump();
expect(callbackTracker, equals(<int>[]));
callbackTracker.clear();
testWidget.flip();
await tester.pump();
expect(callbackTracker, equals(<int>[
0, 1, 2, 3, 4, 5, // visible
6, 7, 8, // in cached area
]));
});
testWidgets('ListView vertical', (WidgetTester tester) async {
final List<int> callbackTracker = <int>[];
// the root view is 800x600 in the test environment
// so if our widget is 200 pixels tall, it should fit exactly 3 times.
// but if we are offset by 300 pixels, there will be 4, numbered 1-4.
final IndexedWidgetBuilder itemBuilder = (BuildContext context, int index) {
callbackTracker.add(index);
return new Container(
key: new ValueKey<int>(index),
width: 500.0, // this should be ignored
height: 200.0,
child: new Text('$index', textDirection: TextDirection.ltr),
);
};
Widget builder() {
return new Directionality(
textDirection: TextDirection.ltr,
child: new FlipWidget(
left: new ListView.builder(
controller: new ScrollController(initialScrollOffset: 300.0),
itemBuilder: itemBuilder,
),
right: const Text('Not Today'),
),
);
}
await tester.pumpWidget(builder());
// 0 is built to find its height
expect(callbackTracker, equals(<int>[
0, 1, 2, 3, 4,
5, // in cached area
]));
callbackTracker.clear();
final ScrollableState scrollable = tester.state(find.byType(Scrollable));
scrollable.position.jumpTo(600.0); // now only 3 should fit, numbered 3-5.
await tester.pumpWidget(builder());
// We build the visible children to find their new size.
expect(callbackTracker, equals(<int>[
0, 1, 2,
3, 4, 5, //visible
6, 7
]));
callbackTracker.clear();
await tester.pumpWidget(builder());
// 0 isn't built because they're not visible.
expect(callbackTracker, equals(<int>[
1, 2,
3, 4, 5, // visible
6, 7,
]
));
callbackTracker.clear();
});
testWidgets('ListView horizontal', (WidgetTester tester) async {
final List<int> callbackTracker = <int>[];
// the root view is 800x600 in the test environment
// so if our widget is 200 pixels wide, it should fit exactly 4 times.
// but if we are offset by 300 pixels, there will be 5, numbered 1-5.
final IndexedWidgetBuilder itemBuilder = (BuildContext context, int index) {
callbackTracker.add(index);
return new Container(
key: new ValueKey<int>(index),
height: 500.0, // this should be ignored
width: 200.0,
child: new Text('$index', textDirection: TextDirection.ltr),
);
};
Widget builder() {
return new Directionality(
textDirection: TextDirection.ltr,
child: new FlipWidget(
left: new ListView.builder(
scrollDirection: Axis.horizontal,
controller: new ScrollController(initialScrollOffset: 500.0),
itemBuilder: itemBuilder,
),
right: const Text('Not Today'),
),
);
}
await tester.pumpWidget(builder());
// 0 is built to find its width
expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4, 5, 6, 7]));
callbackTracker.clear();
final ScrollableState scrollable = tester.state(find.byType(Scrollable));
scrollable.position.jumpTo(600.0); // now only 4 should fit, numbered 2-5.
await tester.pumpWidget(builder());
// We build the visible children to find their new size.
expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5, 6, 7, 8]));
callbackTracker.clear();
await tester.pumpWidget(builder());
// 0 isn't built because they're not visible.
expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5, 6, 7, 8]));
callbackTracker.clear();
});
testWidgets('ListView reinvoke builders', (WidgetTester tester) async {
final List<int> callbackTracker = <int>[];
final List<String> text = <String>[];
final IndexedWidgetBuilder itemBuilder = (BuildContext context, int index) {
callbackTracker.add(index);
return new Container(
key: new ValueKey<int>(index),
width: 500.0, // this should be ignored
height: 220.0,
child: new Text('$index', textDirection: TextDirection.ltr)
);
};
void collectText(Widget widget) {
if (widget is Text)
text.add(widget.data);
}
Widget builder() {
return new Directionality(
textDirection: TextDirection.ltr,
child: new ListView.builder(
itemBuilder: itemBuilder,
),
);
}
await tester.pumpWidget(builder());
expect(callbackTracker, equals(<int>[
0, 1, 2,
3, // in cached area
]));
callbackTracker.clear();
tester.allWidgets.forEach(collectText);
expect(text, equals(<String>['0', '1', '2', '3']));
text.clear();
await tester.pumpWidget(builder());
expect(callbackTracker, equals(<int>[
0, 1, 2,
3, // in cached area
]));
callbackTracker.clear();
tester.allWidgets.forEach(collectText);
expect(text, equals(<String>['0', '1', '2', '3']));
text.clear();
});
testWidgets('ListView reinvoke builders', (WidgetTester tester) async {
StateSetter setState;
ThemeData themeData = new ThemeData.light();
final IndexedWidgetBuilder itemBuilder = (BuildContext context, int index) {
return new Container(
key: new ValueKey<int>(index),
width: 500.0, // this should be ignored
height: 220.0,
color: Theme.of(context).primaryColor,
child: new Text('$index', textDirection: TextDirection.ltr),
);
};
final Widget viewport = new ListView.builder(
itemBuilder: itemBuilder,
);
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setter) {
setState = setter;
return new Theme(data: themeData, child: viewport);
},
),
),
);
DecoratedBox widget = tester.firstWidget(find.byType(DecoratedBox));
BoxDecoration decoration = widget.decoration;
expect(decoration.color, equals(Colors.blue));
setState(() {
themeData = new ThemeData(primarySwatch: Colors.green);
});
await tester.pump();
widget = tester.firstWidget(find.byType(DecoratedBox));
decoration = widget.decoration;
expect(decoration.color, equals(Colors.green));
});
testWidgets('ListView padding', (WidgetTester tester) async {
final IndexedWidgetBuilder itemBuilder = (BuildContext context, int index) {
return new Container(
key: new ValueKey<int>(index),
width: 500.0, // this should be ignored
height: 220.0,
color: Colors.green[500],
child: new Text('$index', textDirection: TextDirection.ltr),
);
};
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new ListView.builder(
padding: const EdgeInsets.fromLTRB(7.0, 3.0, 5.0, 11.0),
itemBuilder: itemBuilder,
),
),
);
final RenderBox firstBox = tester.renderObject(find.text('0'));
final Offset upperLeft = firstBox.localToGlobal(Offset.zero);
expect(upperLeft, equals(const Offset(7.0, 3.0)));
expect(firstBox.size.width, equals(800.0 - 12.0));
});
testWidgets('ListView underflow extents', (WidgetTester tester) async {
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new ListView(
addAutomaticKeepAlives: false,
children: <Widget>[
new Container(height: 100.0),
new Container(height: 100.0),
new Container(height: 100.0),
],
),
),
);
final RenderSliverList list = tester.renderObject(find.byType(SliverList));
expect(list.indexOf(list.firstChild), equals(0));
expect(list.indexOf(list.lastChild), equals(2));
expect(list.childScrollOffset(list.firstChild), equals(0.0));
expect(list.geometry.scrollExtent, equals(300.0));
expect(list, hasAGoodToStringDeep);
expect(
list.toStringDeep(minLevel: DiagnosticLevel.info),
equalsIgnoringHashCodes(
'RenderSliverList#00000 relayoutBoundary=up1\n'
' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: SliverConstraints(AxisDirection.down,\n'
' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
' │ crossAxisDirection: AxisDirection.right,\n'
' │ viewportMainAxisExtent: 600.0, remainingCacheExtent: 850.0\n'
' │ cacheOrigin: 0.0 )\n'
' │ geometry: SliverGeometry(scrollExtent: 300.0, paintExtent: 300.0,\n'
' │ maxPaintExtent: 300.0, cacheExtent: 300.0)\n'
' │ currently live children: 0 to 2\n'
' │\n'
' ├─child with index 0: RenderRepaintBoundary#00000 relayoutBoundary=up2\n'
' │ │ parentData: index=0; layoutOffset=0.0 (can use size)\n'
' │ │ constraints: BoxConstraints(w=800.0, 0.0<=h<=Infinity)\n'
' │ │ layer: OffsetLayer#00000\n'
' │ │ size: Size(800.0, 100.0)\n'
' │ │ metrics: 0.0% useful (1 bad vs 0 good)\n'
' │ │ diagnosis: insufficient data to draw conclusion (less than five\n'
' │ │ repaints)\n'
' │ │\n'
' │ └─child: RenderConstrainedBox#00000 relayoutBoundary=up3\n'
' │ │ parentData: <none> (can use size)\n'
' │ │ constraints: BoxConstraints(w=800.0, 0.0<=h<=Infinity)\n'
' │ │ size: Size(800.0, 100.0)\n'
' │ │ additionalConstraints: BoxConstraints(0.0<=w<=Infinity, h=100.0)\n'
' │ │\n'
' │ └─child: RenderLimitedBox#00000\n'
' │ │ parentData: <none> (can use size)\n'
' │ │ constraints: BoxConstraints(w=800.0, h=100.0)\n'
' │ │ size: Size(800.0, 100.0)\n'
' │ │ maxWidth: 0.0\n'
' │ │ maxHeight: 0.0\n'
' │ │\n'
' │ └─child: RenderConstrainedBox#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=100.0)\n'
' │ size: Size(800.0, 100.0)\n'
' │ additionalConstraints: BoxConstraints(biggest)\n'
' │\n'
' ├─child with index 1: RenderRepaintBoundary#00000 relayoutBoundary=up2\n'
' │ │ parentData: index=1; layoutOffset=100.0 (can use size)\n'
' │ │ constraints: BoxConstraints(w=800.0, 0.0<=h<=Infinity)\n'
' │ │ layer: OffsetLayer#00000\n'
' │ │ size: Size(800.0, 100.0)\n'
' │ │ metrics: 0.0% useful (1 bad vs 0 good)\n'
' │ │ diagnosis: insufficient data to draw conclusion (less than five\n'
' │ │ repaints)\n'
' │ │\n'
' │ └─child: RenderConstrainedBox#00000 relayoutBoundary=up3\n'
' │ │ parentData: <none> (can use size)\n'
' │ │ constraints: BoxConstraints(w=800.0, 0.0<=h<=Infinity)\n'
' │ │ size: Size(800.0, 100.0)\n'
' │ │ additionalConstraints: BoxConstraints(0.0<=w<=Infinity, h=100.0)\n'
' │ │\n'
' │ └─child: RenderLimitedBox#00000\n'
' │ │ parentData: <none> (can use size)\n'
' │ │ constraints: BoxConstraints(w=800.0, h=100.0)\n'
' │ │ size: Size(800.0, 100.0)\n'
' │ │ maxWidth: 0.0\n'
' │ │ maxHeight: 0.0\n'
' │ │\n'
' │ └─child: RenderConstrainedBox#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=100.0)\n'
' │ size: Size(800.0, 100.0)\n'
' │ additionalConstraints: BoxConstraints(biggest)\n'
' │\n'
' └─child with index 2: RenderRepaintBoundary#00000 relayoutBoundary=up2\n'
' │ parentData: index=2; layoutOffset=200.0 (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, 0.0<=h<=Infinity)\n'
' │ layer: OffsetLayer#00000\n'
' │ size: Size(800.0, 100.0)\n'
' │ metrics: 0.0% useful (1 bad vs 0 good)\n'
' │ diagnosis: insufficient data to draw conclusion (less than five\n'
' │ repaints)\n'
' │\n'
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up3\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, 0.0<=h<=Infinity)\n'
' │ size: Size(800.0, 100.0)\n'
' │ additionalConstraints: BoxConstraints(0.0<=w<=Infinity, h=100.0)\n'
' │\n'
' └─child: RenderLimitedBox#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=100.0)\n'
' │ size: Size(800.0, 100.0)\n'
' │ maxWidth: 0.0\n'
' │ maxHeight: 0.0\n'
' │\n'
' └─child: RenderConstrainedBox#00000\n'
' parentData: <none> (can use size)\n'
' constraints: BoxConstraints(w=800.0, h=100.0)\n'
' size: Size(800.0, 100.0)\n'
' additionalConstraints: BoxConstraints(biggest)\n'
),
);
final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
expect(position.viewportDimension, equals(600.0));
expect(position.minScrollExtent, equals(0.0));
});
}