blob: 06cec88225cf1896a961415b4ebad525076a74d2 [file] [log] [blame]
// Copyright 2018 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 'dart:io';
import 'package:flutter/widgets.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart';
dynamic getRenderSegmentedControl(WidgetTester tester) {
return tester.allRenderObjects.firstWhere(
(RenderObject currentObject) {
return currentObject.toStringShort().contains('_RenderSegmentedControl');
},
);
}
StatefulBuilder setupSimpleSegmentedControl() {
final Map<int, Widget> children = <int, Widget>{};
children[0] = const Text('Child 1');
children[1] = const Text('Child 2');
int sharedValue = 0;
return new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return boilerplate(
child: new SegmentedControl<int>(
children: children,
onValueChanged: (int newValue) {
setState(() {
sharedValue = newValue;
});
},
groupValue: sharedValue,
),
);
},
);
}
Widget boilerplate({Widget child}) {
return new Directionality(
textDirection: TextDirection.ltr,
child: new Center(child: child),
);
}
void main() {
testWidgets('Tap changes toggle state', (WidgetTester tester) async {
final Map<int, Widget> children = <int, Widget>{};
children[0] = const Text('Child 1');
children[1] = const Text('Child 2');
children[2] = const Text('Child 3');
int sharedValue = 0;
await tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return boilerplate(
child: new SegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
onValueChanged: (int newValue) {
setState(() {
sharedValue = newValue;
});
},
groupValue: sharedValue,
),
);
},
),
);
expect(sharedValue, 0);
await tester.tap(find.byKey(const ValueKey<String>('Segmented Control')));
expect(sharedValue, 1);
});
testWidgets('Need at least 2 children', (WidgetTester tester) async {
final Map<int, Widget> children = <int, Widget>{};
try {
await tester.pumpWidget(
boilerplate(
child: new SegmentedControl<int>(
children: children,
onValueChanged: (int newValue) {},
),
),
);
fail(
'Should not be possible to create a segmented control with no children');
} on AssertionError catch (e) {
expect(e.toString(), contains('children.length'));
}
try {
children[0] = const Text('Child 1');
await tester.pumpWidget(
boilerplate(
child: new SegmentedControl<int>(
children: children,
onValueChanged: (int newValue) {},
),
),
);
fail(
'Should not be possible to create a segmented control with just one child');
} on AssertionError catch (e) {
expect(e.toString(), contains('children.length'));
}
});
testWidgets('Value attribute must be the key of one of the children widgets',
(WidgetTester tester) async {
final Map<int, Widget> children = <int, Widget>{};
children[0] = const Text('Child 1');
children[1] = const Text('Child 2');
try {
await tester.pumpWidget(
boilerplate(
child: new SegmentedControl<int>(
children: children,
onValueChanged: (int newValue) {},
groupValue: 2,
),
),
);
fail('Should not be possible to create segmented control in which '
'value is not the key of one of the children widgets');
} on AssertionError catch (e) {
expect(e.toString(), contains('children'));
}
});
testWidgets('Children and onValueChanged can not be null',
(WidgetTester tester) async {
try {
await tester.pumpWidget(
boilerplate(
child: new SegmentedControl<int>(
children: null,
onValueChanged: (int newValue) {},
),
),
);
fail(
'Should not be possible to create segmented control with null children');
} on AssertionError catch (e) {
expect(e.toString(), contains('children'));
}
final Map<int, Widget> children = <int, Widget>{};
children[0] = const Text('Child 1');
children[1] = const Text('Child 2');
try {
await tester.pumpWidget(
boilerplate(
child: new SegmentedControl<int>(
children: children,
onValueChanged: null,
),
),
);
fail(
'Should not be possible to create segmented control with null onValueChanged');
} on AssertionError catch (e) {
expect(e.toString(), contains('onValueChanged'));
}
});
testWidgets(
'Widgets have correct default text/icon styles, change correctly on selection',
(WidgetTester tester) async {
final Map<int, Widget> children = <int, Widget>{};
children[0] = const Text('Child 1');
children[1] = const Icon(IconData(1));
int sharedValue = 0;
await tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return boilerplate(
child: new SegmentedControl<int>(
children: children,
onValueChanged: (int newValue) {
setState(() {
sharedValue = newValue;
});
},
groupValue: sharedValue,
),
);
},
),
);
DefaultTextStyle textStyle =
tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1'));
IconTheme iconTheme =
tester.widget(find.widgetWithIcon(IconTheme, const IconData(1)));
expect(textStyle.style.color, CupertinoColors.white);
expect(iconTheme.data.color, CupertinoColors.activeBlue);
await tester.tap(find.widgetWithIcon(IconTheme, const IconData(1)));
await tester.pump();
textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1'));
iconTheme =
tester.widget(find.widgetWithIcon(IconTheme, const IconData(1)));
expect(textStyle.style.color, CupertinoColors.activeBlue);
expect(iconTheme.data.color, CupertinoColors.white);
});
testWidgets('Tap calls onValueChanged', (WidgetTester tester) async {
final Map<int, Widget> children = <int, Widget>{};
children[0] = const Text('Child 1');
children[1] = const Text('Child 2');
bool value = false;
await tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return boilerplate(
child: new SegmentedControl<int>(
children: children,
onValueChanged: (int newValue) {
value = true;
},
),
);
},
),
);
expect(value, isFalse);
await tester.tap(find.text('Child 2'));
expect(value, isTrue);
});
testWidgets(
'State does not change if onValueChanged does not call setState()',
(WidgetTester tester) async {
final Map<int, Widget> children = <int, Widget>{};
children[0] = const Text('Child 1');
children[1] = const Text('Child 2');
const int sharedValue = 0;
await tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return boilerplate(
child: new SegmentedControl<int>(
children: children,
onValueChanged: (int newValue) {},
groupValue: sharedValue,
),
);
},
),
);
final dynamic childList =
getRenderSegmentedControl(tester).getChildrenAsList();
expect(
getRenderSegmentedControl(tester),
paints
..rrect(
rrect: childList.elementAt(0).parentData.surroundingRect,
color: CupertinoColors.activeBlue,
),
);
expect(
getRenderSegmentedControl(tester),
paints
..rrect()
..rrect()
..rrect(
rrect: childList.elementAt(1).parentData.surroundingRect,
color: CupertinoColors.white,
),
);
await tester.tap(find.text('Child 2'));
await tester.pump();
expect(
getRenderSegmentedControl(tester),
paints
..rrect(
rrect: childList.elementAt(0).parentData.surroundingRect,
color: CupertinoColors.activeBlue,
),
);
expect(
getRenderSegmentedControl(tester),
paints
..rrect()
..rrect()
..rrect(
rrect: childList.elementAt(1).parentData.surroundingRect,
color: CupertinoColors.white,
),
);
});
testWidgets(
'Background color of child should change on selection, '
'and should not change when tapped again', (WidgetTester tester) async {
await tester.pumpWidget(setupSimpleSegmentedControl());
final dynamic childList =
getRenderSegmentedControl(tester).getChildrenAsList();
expect(
getRenderSegmentedControl(tester),
paints
..rrect()
..rrect()
..rrect(
rrect: childList.elementAt(1).parentData.surroundingRect,
color: CupertinoColors.white,
),
);
await tester.tap(find.text('Child 2'));
await tester.pump();
expect(
getRenderSegmentedControl(tester),
paints
..rrect()
..rrect()
..rrect(
rrect: childList.elementAt(1).parentData.surroundingRect,
color: CupertinoColors.activeBlue,
),
);
await tester.tap(find.text('Child 2'));
await tester.pump();
expect(
getRenderSegmentedControl(tester),
paints
..rrect()
..rrect()
..rrect(
rrect: childList.elementAt(1).parentData.surroundingRect,
color: CupertinoColors.activeBlue,
),
);
});
testWidgets(
'Children can be non-Text or Icon widgets (in this case, '
'a Container or Placeholder widget)',
(WidgetTester tester) async {
final Map<int, Widget> children = <int, Widget>{};
children[0] = const Text('Child 1');
children[1] = new Container(
constraints: const BoxConstraints.tightFor(width: 50.0, height: 50.0),
);
children[2] = const Placeholder();
int sharedValue = 0;
await tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return boilerplate(
child: new SegmentedControl<int>(
children: children,
onValueChanged: (int newValue) {
setState(() {
sharedValue = newValue;
});
},
groupValue: sharedValue,
),
);
},
),
);
},
);
testWidgets('Passed in value is child initially selected',
(WidgetTester tester) async {
await tester.pumpWidget(setupSimpleSegmentedControl());
expect(getRenderSegmentedControl(tester).selectedIndex, 0);
final dynamic childList =
getRenderSegmentedControl(tester).getChildrenAsList();
expect(
getRenderSegmentedControl(tester),
paints
..rrect(
rrect: childList.elementAt(0).parentData.surroundingRect,
color: CupertinoColors.activeBlue,
),
);
expect(
getRenderSegmentedControl(tester),
paints
..rrect()
..rrect()
..rrect(
rrect: childList.elementAt(1).parentData.surroundingRect,
color: CupertinoColors.white,
),
);
});
testWidgets('Null input for value results in no child initially selected',
(WidgetTester tester) async {
final Map<int, Widget> children = <int, Widget>{};
children[0] = const Text('Child 1');
children[1] = const Text('Child 2');
int sharedValue;
await tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return boilerplate(
child: new SegmentedControl<int>(
children: children,
onValueChanged: (int newValue) {
setState(() {
sharedValue = newValue;
});
},
groupValue: sharedValue,
),
);
},
),
);
expect(getRenderSegmentedControl(tester).selectedIndex, null);
final dynamic childList =
getRenderSegmentedControl(tester).getChildrenAsList();
expect(
getRenderSegmentedControl(tester),
paints
..rrect(
rrect: childList.elementAt(0).parentData.surroundingRect,
color: CupertinoColors.white,
),
);
expect(
getRenderSegmentedControl(tester),
paints
..rrect()
..rrect()
..rrect(
rrect: childList.elementAt(1).parentData.surroundingRect,
color: CupertinoColors.white,
),
);
});
testWidgets('Long press changes background color of not-selected child',
(WidgetTester tester) async {
await tester.pumpWidget(setupSimpleSegmentedControl());
final dynamic childList =
getRenderSegmentedControl(tester).getChildrenAsList();
expect(
getRenderSegmentedControl(tester),
paints
..rrect(
rrect: childList.elementAt(0).parentData.surroundingRect,
color: CupertinoColors.activeBlue,
),
);
expect(
getRenderSegmentedControl(tester),
paints
..rrect()
..rrect()
..rrect(
rrect: childList.elementAt(1).parentData.surroundingRect,
color: CupertinoColors.white,
),
);
final Offset center = tester.getCenter(find.text('Child 2'));
await tester.startGesture(center);
await tester.pumpAndSettle();
expect(
getRenderSegmentedControl(tester),
paints
..rrect(
rrect: childList.elementAt(0).parentData.surroundingRect,
color: CupertinoColors.activeBlue,
),
);
expect(
getRenderSegmentedControl(tester),
paints
..rrect()
..rrect()
..rrect(
rrect: childList.elementAt(1).parentData.surroundingRect,
color: const Color(0x33007aff),
),
);
});
testWidgets(
'Long press does not change background color of currently-selected child',
(WidgetTester tester) async {
await tester.pumpWidget(setupSimpleSegmentedControl());
final dynamic childList =
getRenderSegmentedControl(tester).getChildrenAsList();
expect(
getRenderSegmentedControl(tester),
paints
..rrect(
rrect: childList.elementAt(0).parentData.surroundingRect,
color: CupertinoColors.activeBlue,
),
);
expect(
getRenderSegmentedControl(tester),
paints
..rrect()
..rrect()
..rrect(
rrect: childList.elementAt(1).parentData.surroundingRect,
color: CupertinoColors.white,
),
);
final Offset center = tester.getCenter(find.text('Child 1'));
await tester.startGesture(center);
await tester.pumpAndSettle();
expect(
getRenderSegmentedControl(tester),
paints
..rrect(
rrect: childList.elementAt(0).parentData.surroundingRect,
color: CupertinoColors.activeBlue,
),
);
expect(
getRenderSegmentedControl(tester),
paints
..rrect()
..rrect()
..rrect(
rrect: childList.elementAt(1).parentData.surroundingRect,
color: CupertinoColors.white,
),
);
});
testWidgets('Height of segmented control is determined by tallest widget',
(WidgetTester tester) async {
final Map<int, Widget> children = <int, Widget>{};
children[0] = new Container(
constraints: const BoxConstraints.tightFor(height: 100.0),
);
children[1] = new Container(
constraints: const BoxConstraints.tightFor(height: 400.0),
);
children[2] = new Container(
constraints: const BoxConstraints.tightFor(height: 200.0),
);
await tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return boilerplate(
child: new SegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
onValueChanged: (int newValue) {},
),
);
},
),
);
final RenderBox buttonBox = tester
.renderObject(find.byKey(const ValueKey<String>('Segmented Control')));
// Default height of Placeholder is 400.0px, which is greater than heights
// of other child widgets.
expect(buttonBox.size.height, 400.0);
});
testWidgets('Width of each child widget is the same',
(WidgetTester tester) async {
final Map<int, Widget> children = <int, Widget>{};
children[0] = new Container();
children[1] = const Placeholder();
children[2] = new Container();
await tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return boilerplate(
child: new SegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
onValueChanged: (int newValue) {},
),
);
},
),
);
final RenderBox segmentedControl = tester
.renderObject(find.byKey(const ValueKey<String>('Segmented Control')));
// Subtract the 16.0px from each side. Remaining width should be allocated
// to each child equally.
final double childWidth = (segmentedControl.size.width - 32.0) / 3;
final dynamic childList =
getRenderSegmentedControl(tester).getChildrenAsList();
for (dynamic child in childList) {
expect(childWidth, child.parentData.surroundingRect.width);
}
});
testWidgets('Width is finite in unbounded space',
(WidgetTester tester) async {
final Map<int, Widget> children = <int, Widget>{};
children[0] = const Text('Child 1');
children[1] = const Text('Child 2');
await tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return boilerplate(
child: Row(
children: <Widget>[
new SegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
onValueChanged: (int newValue) {},
),
],
),
);
},
),
);
final RenderBox segmentedControl = tester
.renderObject(find.byKey(const ValueKey<String>('Segmented Control')));
expect(segmentedControl.size.width.isFinite, isTrue);
});
testWidgets('Directionality test - RTL should reverse order of widgets',
(WidgetTester tester) async {
final Map<int, Widget> children = <int, Widget>{};
children[0] = const Text('Child 1');
children[1] = const Text('Child 2');
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.rtl,
child: new Center(
child: new SegmentedControl<int>(
children: children,
onValueChanged: (int newValue) {},
),
),
),
);
expect(
tester.getTopRight(find.text('Child 1')).dx >
tester.getTopRight(find.text('Child 2')).dx,
isTrue);
});
testWidgets('Correct initial selection and toggling behavior - RTL',
(WidgetTester tester) async {
final Map<int, Widget> children = <int, Widget>{};
children[0] = const Text('Child 1');
children[1] = const Text('Child 2');
int sharedValue = 0;
await tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Directionality(
textDirection: TextDirection.rtl,
child: new Center(
child: new SegmentedControl<int>(
children: children,
onValueChanged: (int newValue) {
setState(() {
sharedValue = newValue;
});
},
groupValue: sharedValue,
),
),
);
},
),
);
final dynamic childList =
getRenderSegmentedControl(tester).getChildrenAsList();
expect(
getRenderSegmentedControl(tester),
paints
..rrect(
rrect: childList.elementAt(0).parentData.surroundingRect,
color: CupertinoColors.activeBlue,
),
);
expect(
getRenderSegmentedControl(tester),
paints
..rrect()
..rrect()
..rrect(
rrect: childList.elementAt(1).parentData.surroundingRect,
color: CupertinoColors.white,
),
);
await tester.tap(find.text('Child 2'));
await tester.pump();
expect(
getRenderSegmentedControl(tester),
paints
..rrect(
rrect: childList.elementAt(0).parentData.surroundingRect,
color: CupertinoColors.white,
),
);
expect(
getRenderSegmentedControl(tester),
paints
..rrect()
..rrect()
..rrect(
rrect: childList.elementAt(1).parentData.surroundingRect,
color: CupertinoColors.activeBlue,
),
);
await tester.tap(find.text('Child 2'));
await tester.pump();
expect(
getRenderSegmentedControl(tester),
paints
..rrect()
..rrect()
..rrect(
rrect: childList.elementAt(1).parentData.surroundingRect,
color: CupertinoColors.activeBlue,
),
);
});
testWidgets('Segmented control semantics', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
final Map<int, Widget> children = <int, Widget>{};
children[0] = const Text('Child 1');
children[1] = const Text('Child 2');
int sharedValue = 0;
await tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Directionality(
textDirection: TextDirection.ltr,
child: new Center(
child: new SegmentedControl<int>(
children: children,
onValueChanged: (int newValue) {
setState(() {
sharedValue = newValue;
});
},
groupValue: sharedValue,
),
),
);
},
),
);
expect(
semantics,
hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
label: 'Child 1',
flags: <SemanticsFlag>[
SemanticsFlag.isInMutuallyExclusiveGroup,
SemanticsFlag.isSelected,
],
actions: <SemanticsAction>[
SemanticsAction.tap,
],
),
new TestSemantics.rootChild(
label: 'Child 2',
flags: <SemanticsFlag>[
SemanticsFlag.isInMutuallyExclusiveGroup,
],
actions: <SemanticsAction>[
SemanticsAction.tap,
],
),
],
),
ignoreId: true,
ignoreRect: true,
ignoreTransform: true,
));
await tester.tap(find.text('Child 2'));
await tester.pump();
expect(
semantics,
hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
label: 'Child 1',
flags: <SemanticsFlag>[
SemanticsFlag.isInMutuallyExclusiveGroup,
],
actions: <SemanticsAction>[
SemanticsAction.tap,
],
),
new TestSemantics.rootChild(
label: 'Child 2',
flags: <SemanticsFlag>[
SemanticsFlag.isInMutuallyExclusiveGroup,
SemanticsFlag.isSelected,
],
actions: <SemanticsAction>[
SemanticsAction.tap,
],
),
],
),
ignoreId: true,
ignoreRect: true,
ignoreTransform: true,
));
semantics.dispose();
});
testWidgets('Golden Test Placeholder Widget', (WidgetTester tester) async {
// Different machines render this content differently. Since the golden
// files are rendered on MacOS, this test should only be run on MacOS.
// If the golden files are regenerated on another OS, please change this
// test to only run on that OS.
if (Platform.isMacOS)
return;
final Map<int, Widget> children = <int, Widget>{};
children[0] = new Container();
children[1] = const Placeholder();
children[2] = new Container();
const int currentValue = 0;
await tester.pumpWidget(
new RepaintBoundary(
child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return boilerplate(
child: new SegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
onValueChanged: (int newValue) {},
groupValue: currentValue,
),
);
},
),
),
);
await expectLater(
find.byType(RepaintBoundary),
matchesGoldenFile('segmented_control_test.0.0.png'),
);
});
testWidgets('Golden Test Pressed State', (WidgetTester tester) async {
// Different machines render this content differently. Since the golden
// files are rendered on MacOS, this test should only be run on MacOS.
// If the golden files are regenerated on another OS, please change this
// test to only run on that OS.
if (!Platform.isMacOS)
return;
final Map<int, Widget> children = <int, Widget>{};
children[0] = const Text('A');
children[1] = const Text('B');
children[2] = const Text('C');
const int currentValue = 0;
await tester.pumpWidget(
new RepaintBoundary(
child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return boilerplate(
child: new SegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
onValueChanged: (int newValue) {},
groupValue: currentValue,
),
);
},
),
),
);
final Offset center = tester.getCenter(find.text('B'));
await tester.startGesture(center);
await tester.pumpAndSettle();
await expectLater(
find.byType(RepaintBoundary),
matchesGoldenFile('segmented_control_test.1.0.png'),
);
});
}