1912 lines
61 KiB
Dart
1912 lines
61 KiB
Dart
// 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 'dart:ui' as ui;
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import '../widgets/semantics_tester.dart';
|
|
|
|
RenderBox getRenderSegmentedControl(WidgetTester tester) {
|
|
return tester.allRenderObjects.firstWhere((RenderObject currentObject) {
|
|
return currentObject.toStringShort().contains('_RenderSegmentedControl');
|
|
})
|
|
as RenderBox;
|
|
}
|
|
|
|
StatefulBuilder setupSimpleSegmentedControl() {
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = const Text('Child 2');
|
|
var sharedValue = 0;
|
|
|
|
return StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget boilerplate({required Widget child}) {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(child: child),
|
|
);
|
|
}
|
|
|
|
int getChildCount(WidgetTester tester) {
|
|
return (getRenderSegmentedControl(tester)
|
|
as RenderBoxContainerDefaultsMixin<RenderBox, ContainerBoxParentData<RenderBox>>)
|
|
.getChildrenAsList()
|
|
.length;
|
|
}
|
|
|
|
ui.RSuperellipse getSurroundingShape(WidgetTester tester, {int child = 0}) {
|
|
return ((getRenderSegmentedControl(tester)
|
|
as RenderBoxContainerDefaultsMixin<
|
|
RenderBox,
|
|
ContainerBoxParentData<RenderBox>
|
|
>)
|
|
.getChildrenAsList()[child]
|
|
.parentData!
|
|
as dynamic)
|
|
.surroundingRect
|
|
as ui.RSuperellipse;
|
|
}
|
|
|
|
Size getChildSize(WidgetTester tester, {int child = 0}) {
|
|
return (getRenderSegmentedControl(tester)
|
|
as RenderBoxContainerDefaultsMixin<RenderBox, ContainerBoxParentData<RenderBox>>)
|
|
.getChildrenAsList()[child]
|
|
.size;
|
|
}
|
|
|
|
Color getBorderColor(WidgetTester tester) {
|
|
return (getRenderSegmentedControl(tester) as dynamic).borderColor as Color;
|
|
}
|
|
|
|
int? getSelectedIndex(WidgetTester tester) {
|
|
return (getRenderSegmentedControl(tester) as dynamic).selectedIndex as int?;
|
|
}
|
|
|
|
Color getBackgroundColor(WidgetTester tester, int childIndex) {
|
|
// Using dynamic so the test can access a private class.
|
|
// ignore: avoid_dynamic_calls
|
|
return (getRenderSegmentedControl(tester) as dynamic).backgroundColors[childIndex] as Color;
|
|
}
|
|
|
|
void main() {
|
|
testWidgets('Tap changes toggle state', (WidgetTester tester) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = const Text('Child 2');
|
|
children[2] = const Text('Child 3');
|
|
|
|
var sharedValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<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 {
|
|
await expectLater(
|
|
() => tester.pumpWidget(
|
|
boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: const <int, Widget>{},
|
|
onValueChanged: (int newValue) {},
|
|
),
|
|
),
|
|
),
|
|
throwsA(
|
|
isAssertionError.having(
|
|
(AssertionError error) => error.toString(),
|
|
'.toString()',
|
|
contains('children.length'),
|
|
),
|
|
),
|
|
);
|
|
|
|
await expectLater(
|
|
() => tester.pumpWidget(
|
|
boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: const <int, Widget>{0: Text('Child 1')},
|
|
onValueChanged: (int newValue) {},
|
|
),
|
|
),
|
|
),
|
|
throwsA(
|
|
isAssertionError.having(
|
|
(AssertionError error) => error.toString(),
|
|
'.toString()',
|
|
contains('children.length'),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Padding works', (WidgetTester tester) async {
|
|
const key = Key('Container');
|
|
|
|
final children = <int, Widget>{};
|
|
children[0] = const SizedBox(height: double.infinity, child: Text('Child 1'));
|
|
children[1] = const SizedBox(height: double.infinity, child: Text('Child 2'));
|
|
|
|
Future<void> verifyPadding({EdgeInsets? padding}) async {
|
|
final EdgeInsets effectivePadding = padding ?? const EdgeInsets.symmetric(horizontal: 16);
|
|
final Rect segmentedControlRect = tester.getRect(find.byKey(key));
|
|
expect(
|
|
tester.getTopLeft(find.byWidget(children[0]!)),
|
|
segmentedControlRect.topLeft.translate(
|
|
effectivePadding.topLeft.dx,
|
|
effectivePadding.topLeft.dy,
|
|
),
|
|
);
|
|
expect(
|
|
tester.getBottomLeft(find.byWidget(children[0]!)),
|
|
segmentedControlRect.bottomLeft.translate(
|
|
effectivePadding.bottomLeft.dx,
|
|
effectivePadding.bottomLeft.dy,
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.getTopRight(find.byWidget(children[1]!)),
|
|
segmentedControlRect.topRight.translate(
|
|
effectivePadding.topRight.dx,
|
|
effectivePadding.topRight.dy,
|
|
),
|
|
);
|
|
expect(
|
|
tester.getBottomRight(find.byWidget(children[1]!)),
|
|
segmentedControlRect.bottomRight.translate(
|
|
effectivePadding.bottomRight.dx,
|
|
effectivePadding.bottomRight.dy,
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
key: key,
|
|
children: children,
|
|
onValueChanged: (int newValue) {},
|
|
),
|
|
),
|
|
);
|
|
|
|
// Default padding works.
|
|
await verifyPadding();
|
|
|
|
// Switch to Child 2 padding should remain the same.
|
|
await tester.tap(find.text('Child 2'));
|
|
await tester.pumpAndSettle();
|
|
|
|
await verifyPadding();
|
|
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
key: key,
|
|
padding: const EdgeInsets.fromLTRB(1, 3, 5, 7),
|
|
children: children,
|
|
onValueChanged: (int newValue) {},
|
|
),
|
|
),
|
|
);
|
|
|
|
// Custom padding works.
|
|
await verifyPadding(padding: const EdgeInsets.fromLTRB(1, 3, 5, 7));
|
|
|
|
// Switch back to Child 1 padding should remain the same.
|
|
await tester.tap(find.text('Child 1'));
|
|
await tester.pumpAndSettle();
|
|
|
|
await verifyPadding(padding: const EdgeInsets.fromLTRB(1, 3, 5, 7));
|
|
});
|
|
|
|
testWidgets('Value attribute must be the key of one of the children widgets', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = const Text('Child 2');
|
|
|
|
await expectLater(
|
|
() => tester.pumpWidget(
|
|
boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
onValueChanged: (int newValue) {},
|
|
groupValue: 2,
|
|
),
|
|
),
|
|
),
|
|
throwsA(
|
|
isAssertionError.having(
|
|
(AssertionError error) => error.toString(),
|
|
'.toString()',
|
|
contains('children'),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Widgets have correct default text/icon styles, change correctly on selection', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = const Icon(IconData(1));
|
|
|
|
var sharedValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
DefaultTextStyle textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1'));
|
|
IconTheme iconTheme = tester.widget(find.widgetWithIcon(IconTheme, const IconData(1)));
|
|
|
|
expect(textStyle.style.color, isSameColorAs(CupertinoColors.white));
|
|
expect(iconTheme.data.color, CupertinoColors.activeBlue);
|
|
|
|
await tester.tap(find.widgetWithIcon(IconTheme, const IconData(1)));
|
|
await tester.pumpAndSettle();
|
|
|
|
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, isSameColorAs(CupertinoColors.white));
|
|
});
|
|
|
|
testWidgets('Segmented controls respects themes', (WidgetTester tester) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = const Icon(IconData(1));
|
|
|
|
var sharedValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
theme: const CupertinoThemeData(brightness: Brightness.dark),
|
|
home: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
DefaultTextStyle textStyle = tester.widget(
|
|
find.widgetWithText(DefaultTextStyle, 'Child 1').first,
|
|
);
|
|
IconThemeData iconTheme = IconTheme.of(tester.element(find.byIcon(const IconData(1))));
|
|
|
|
expect(textStyle.style.color, isSameColorAs(CupertinoColors.white));
|
|
expect(iconTheme.color, isSameColorAs(CupertinoColors.systemBlue.darkColor));
|
|
|
|
await tester.tap(find.byIcon(const IconData(1)));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1').first);
|
|
iconTheme = IconTheme.of(tester.element(find.byIcon(const IconData(1))));
|
|
|
|
expect(textStyle.style.color, isSameColorAs(CupertinoColors.systemBlue.darkColor));
|
|
expect(iconTheme.color, isSameColorAs(CupertinoColors.white));
|
|
});
|
|
|
|
testWidgets('SegmentedControl is correct when user provides custom colors', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = const Icon(IconData(1));
|
|
|
|
var sharedValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
unselectedColor: CupertinoColors.lightBackgroundGray,
|
|
selectedColor: CupertinoColors.activeGreen.color,
|
|
borderColor: CupertinoColors.black,
|
|
pressedColor: const Color(0x638CFC7B),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
DefaultTextStyle textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1'));
|
|
IconTheme iconTheme = tester.widget(find.widgetWithIcon(IconTheme, const IconData(1)));
|
|
|
|
expect(getBorderColor(tester), CupertinoColors.black);
|
|
expect(textStyle.style.color, CupertinoColors.lightBackgroundGray);
|
|
expect(iconTheme.data.color, CupertinoColors.activeGreen.color);
|
|
expect(getBackgroundColor(tester, 0), CupertinoColors.activeGreen.color);
|
|
expect(getBackgroundColor(tester, 1), CupertinoColors.lightBackgroundGray);
|
|
|
|
await tester.tap(find.widgetWithIcon(IconTheme, const IconData(1)));
|
|
await tester.pumpAndSettle();
|
|
|
|
textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1'));
|
|
iconTheme = tester.widget(find.widgetWithIcon(IconTheme, const IconData(1)));
|
|
|
|
expect(textStyle.style.color, CupertinoColors.activeGreen.color);
|
|
expect(iconTheme.data.color, CupertinoColors.lightBackgroundGray);
|
|
expect(getBackgroundColor(tester, 0), CupertinoColors.lightBackgroundGray);
|
|
expect(getBackgroundColor(tester, 1), CupertinoColors.activeGreen.color);
|
|
|
|
final Offset center = tester.getCenter(find.text('Child 1'));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getBackgroundColor(tester, 0), const Color(0x638CFC7B));
|
|
expect(getBackgroundColor(tester, 1), CupertinoColors.activeGreen.color);
|
|
|
|
// Finish gesture to release resources.
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('Widgets are centered within segments', (WidgetTester tester) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = const Text('Child 2');
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Align(
|
|
alignment: Alignment.topLeft,
|
|
child: SizedBox(
|
|
width: 200.0,
|
|
height: 200.0,
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
onValueChanged: (int newValue) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Widgets are centered taking into account 16px of horizontal padding
|
|
expect(tester.getCenter(find.text('Child 1')), const Offset(58.0, 100.0));
|
|
expect(tester.getCenter(find.text('Child 2')), const Offset(142.0, 100.0));
|
|
});
|
|
|
|
testWidgets('Tap calls onValueChanged', (WidgetTester tester) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = const Text('Child 2');
|
|
|
|
var value = false;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<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 children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = const Text('Child 2');
|
|
|
|
const sharedValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
onValueChanged: (int newValue) {},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white));
|
|
|
|
await tester.tap(find.text('Child 2'));
|
|
await tester.pump();
|
|
|
|
expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(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());
|
|
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white));
|
|
|
|
await tester.tap(find.text('Child 2'));
|
|
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
|
|
|
expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
|
|
|
|
await tester.tap(find.text('Child 2'));
|
|
await tester.pump();
|
|
|
|
expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
|
|
});
|
|
|
|
testWidgets('Children can be non-Text or Icon widgets (in this case, '
|
|
'a Container or Placeholder widget)', (WidgetTester tester) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = Container(constraints: const BoxConstraints.tightFor(width: 50.0, height: 50.0));
|
|
children[2] = const Placeholder();
|
|
|
|
var sharedValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<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(getSelectedIndex(tester), 0);
|
|
|
|
expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white));
|
|
});
|
|
|
|
testWidgets('Null input for value results in no child initially selected', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = const Text('Child 2');
|
|
|
|
int? sharedValue;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(getSelectedIndex(tester), null);
|
|
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.white));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white));
|
|
});
|
|
|
|
testWidgets('Long press changes background color of not-selected child', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(setupSimpleSegmentedControl());
|
|
|
|
expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white));
|
|
|
|
final Offset center = tester.getCenter(find.text('Child 2'));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
|
|
expect(getBackgroundColor(tester, 1), const Color(0x33007aff));
|
|
|
|
// Finish gesture to release resources.
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('Long press does not change background color of currently-selected child', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(setupSimpleSegmentedControl());
|
|
|
|
expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white));
|
|
|
|
final Offset center = tester.getCenter(find.text('Child 1'));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white));
|
|
|
|
// Finish gesture to release resources.
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('Height of segmented control is determined by tallest widget', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = Container(constraints: const BoxConstraints.tightFor(height: 100.0));
|
|
children[1] = Container(constraints: const BoxConstraints.tightFor(height: 400.0));
|
|
children[2] = Container(constraints: const BoxConstraints.tightFor(height: 200.0));
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
key: const ValueKey<String>('Segmented Control'),
|
|
children: children,
|
|
onValueChanged: (int newValue) {},
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
final RenderBox buttonBox = tester.renderObject(
|
|
find.byKey(const ValueKey<String>('Segmented Control')),
|
|
);
|
|
|
|
expect(buttonBox.size.height, 400.0);
|
|
});
|
|
|
|
testWidgets('Width of each segmented control segment is determined by widest widget', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = Container(constraints: const BoxConstraints.tightFor(width: 50.0));
|
|
children[1] = Container(constraints: const BoxConstraints.tightFor(width: 100.0));
|
|
children[2] = Container(constraints: const BoxConstraints.tightFor(width: 200.0));
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<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;
|
|
|
|
expect(childWidth, 200.0);
|
|
|
|
expect(childWidth, getSurroundingShape(tester).width);
|
|
expect(childWidth, getSurroundingShape(tester, child: 1).width);
|
|
expect(childWidth, getSurroundingShape(tester, child: 2).width);
|
|
});
|
|
|
|
testWidgets('Width is finite in unbounded space', (WidgetTester tester) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = const Text('Child 2');
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: Row(
|
|
children: <Widget>[
|
|
CupertinoSegmentedControl<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 children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = const Text('Child 2');
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: Center(
|
|
child: CupertinoSegmentedControl<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 children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = const Text('Child 2');
|
|
|
|
var sharedValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: Center(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white));
|
|
|
|
await tester.tap(find.text('Child 2'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.white));
|
|
expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
|
|
|
|
await tester.tap(find.text('Child 2'));
|
|
await tester.pump();
|
|
|
|
expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
|
|
});
|
|
|
|
testWidgets('Segmented control semantics', (WidgetTester tester) async {
|
|
final semantics = SemanticsTester(tester);
|
|
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = const Text('Child 2');
|
|
var sharedValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(
|
|
semantics,
|
|
hasSemantics(
|
|
TestSemantics.root(
|
|
children: <TestSemantics>[
|
|
TestSemantics.rootChild(
|
|
role: SemanticsRole.radioGroup,
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
label: 'Child 1',
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.isButton,
|
|
SemanticsFlag.isInMutuallyExclusiveGroup,
|
|
SemanticsFlag.hasSelectedState,
|
|
SemanticsFlag.isSelected,
|
|
SemanticsFlag.isFocusable,
|
|
],
|
|
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
|
|
),
|
|
TestSemantics(
|
|
label: 'Child 2',
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.isButton,
|
|
SemanticsFlag.isInMutuallyExclusiveGroup,
|
|
// Declares that it is selectable, but not currently selected.
|
|
SemanticsFlag.hasSelectedState,
|
|
SemanticsFlag.isFocusable,
|
|
],
|
|
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
ignoreId: true,
|
|
ignoreRect: true,
|
|
ignoreTransform: true,
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('Child 2'));
|
|
await tester.pump();
|
|
|
|
expect(
|
|
semantics,
|
|
hasSemantics(
|
|
TestSemantics.root(
|
|
children: <TestSemantics>[
|
|
TestSemantics.rootChild(
|
|
role: SemanticsRole.radioGroup,
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
label: 'Child 1',
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.isButton,
|
|
SemanticsFlag.isInMutuallyExclusiveGroup,
|
|
// Declares that it is selectable, but not currently selected.
|
|
SemanticsFlag.hasSelectedState,
|
|
SemanticsFlag.isFocusable,
|
|
],
|
|
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
|
|
),
|
|
TestSemantics(
|
|
label: 'Child 2',
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.isButton,
|
|
SemanticsFlag.isInMutuallyExclusiveGroup,
|
|
SemanticsFlag.hasSelectedState,
|
|
SemanticsFlag.isSelected,
|
|
SemanticsFlag.isFocusable,
|
|
SemanticsFlag.isFocused,
|
|
],
|
|
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
ignoreId: true,
|
|
ignoreRect: true,
|
|
ignoreTransform: true,
|
|
),
|
|
);
|
|
|
|
semantics.dispose();
|
|
});
|
|
|
|
testWidgets('Non-centered taps work on smaller widgets', (WidgetTester tester) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = const Text('Child 2');
|
|
|
|
var sharedValue = 1;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
key: const ValueKey<String>('Segmented Control'),
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(sharedValue, 1);
|
|
|
|
final double childWidth = getChildSize(tester).width;
|
|
final Offset centerOfSegmentedControl = tester.getCenter(find.text('Child 1'));
|
|
|
|
// Tap just inside segment bounds
|
|
await tester.tapAt(
|
|
Offset(centerOfSegmentedControl.dx + (childWidth / 2) - 10.0, centerOfSegmentedControl.dy),
|
|
);
|
|
|
|
expect(sharedValue, 0);
|
|
});
|
|
|
|
testWidgets('Hit-tests report accurate local position in segments', (WidgetTester tester) async {
|
|
final children = <int, Widget>{};
|
|
late TapDownDetails tapDownDetails;
|
|
children[0] = GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTapDown: (TapDownDetails details) {
|
|
tapDownDetails = details;
|
|
},
|
|
child: const SizedBox(width: 200, height: 200),
|
|
);
|
|
children[1] = const Text('Child 2');
|
|
|
|
var sharedValue = 1;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
key: const ValueKey<String>('Segmented Control'),
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(sharedValue, 1);
|
|
|
|
final Offset segment0GlobalOffset = tester.getTopLeft(find.byWidget(children[0]!));
|
|
await tester.tapAt(segment0GlobalOffset + const Offset(7, 11));
|
|
|
|
expect(tapDownDetails.localPosition, const Offset(7, 11));
|
|
expect(tapDownDetails.globalPosition, segment0GlobalOffset + const Offset(7, 11));
|
|
});
|
|
|
|
testWidgets('Segment still hittable with a child that has no hitbox', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/57326.
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = const SizedBox();
|
|
var sharedValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
key: const ValueKey<String>('Segmented Control'),
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(sharedValue, 0);
|
|
|
|
final Offset centerOfTwo = tester.getCenter(find.byWidget(children[1]!));
|
|
// Tap within the bounds of children[1], but not at the center.
|
|
// children[1] is a SizedBox thus not hittable by itself.
|
|
await tester.tapAt(centerOfTwo + const Offset(10, 0));
|
|
|
|
expect(sharedValue, 1);
|
|
});
|
|
|
|
testWidgets('Animation is correct when the selected segment changes', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(setupSimpleSegmentedControl());
|
|
|
|
await tester.tap(find.text('Child 2'));
|
|
|
|
await tester.pump();
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.activeBlue));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(const Color(0x33007aff)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 40));
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(const Color(0xff3d9aff)));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(const Color(0x64007aff)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 40));
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(const Color(0xff7bbaff)));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(const Color(0x95007aff)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 40));
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(const Color(0xffb9daff)));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(const Color(0xc7007aff)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 40));
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(const Color(0xfff7faff)));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(const Color(0xf8007aff)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 40));
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.white));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.activeBlue));
|
|
});
|
|
|
|
testWidgets('Animation is correct when widget is rebuilt', (WidgetTester tester) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = const Text('Child 2');
|
|
var sharedValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('Child 2'));
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.activeBlue));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(const Color(0x33007aff)));
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
duration: const Duration(milliseconds: 40),
|
|
);
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(const Color(0xff3d9aff)));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(const Color(0x64007aff)));
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
duration: const Duration(milliseconds: 40),
|
|
);
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(const Color(0xff7bbaff)));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(const Color(0x95007aff)));
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
duration: const Duration(milliseconds: 40),
|
|
);
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(const Color(0xffb9daff)));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(const Color(0xc7007aff)));
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
duration: const Duration(milliseconds: 40),
|
|
);
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(const Color(0xfff7faff)));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(const Color(0xf8007aff)));
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
duration: const Duration(milliseconds: 40),
|
|
);
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.white));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.activeBlue));
|
|
});
|
|
|
|
testWidgets('Multiple segments are pressed', (WidgetTester tester) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('A');
|
|
children[1] = const Text('B');
|
|
children[2] = const Text('C');
|
|
var sharedValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
key: const ValueKey<String>('Segmented Control'),
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white));
|
|
|
|
final TestGesture gesture1 = await tester.startGesture(tester.getCenter(find.text('B')));
|
|
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
|
|
|
expect(getBackgroundColor(tester, 1), const Color(0x33007aff));
|
|
expect(getBackgroundColor(tester, 2), isSameColorAs(CupertinoColors.white));
|
|
|
|
final TestGesture gesture2 = await tester.startGesture(tester.getCenter(find.text('C')));
|
|
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
|
|
|
// Press on C has no effect while B is held down.
|
|
expect(getBackgroundColor(tester, 1), const Color(0x33007aff));
|
|
expect(getBackgroundColor(tester, 2), isSameColorAs(CupertinoColors.white));
|
|
|
|
// Finish gesture to release resources.
|
|
await gesture1.up();
|
|
await gesture2.up();
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('Transition is triggered while a transition is already occurring', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('A');
|
|
children[1] = const Text('B');
|
|
children[2] = const Text('C');
|
|
var sharedValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
key: const ValueKey<String>('Segmented Control'),
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('B'));
|
|
|
|
await tester.pump();
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.activeBlue));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(const Color(0x33007aff)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 40));
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(const Color(0xff3d9aff)));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(const Color(0x64007aff)));
|
|
|
|
// While A to B transition is occurring, press on C.
|
|
await tester.tap(find.text('C'));
|
|
|
|
await tester.pump();
|
|
|
|
// A and B are now both transitioning to white.
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(const Color(0xff3d9aff)));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(const Color(0xffc1deff)));
|
|
expect(getBackgroundColor(tester, 2), isSameColorAs(const Color(0x33007aff)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 40));
|
|
// B background color has reached unselected state.
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(const Color(0xff7bbaff)));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white));
|
|
expect(getBackgroundColor(tester, 2), isSameColorAs(const Color(0x64007aff)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
// A background color has reached unselected state.
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.white));
|
|
expect(getBackgroundColor(tester, 2), isSameColorAs(const Color(0xe0007aff)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 40));
|
|
// C background color has reached selected state.
|
|
expect(getBackgroundColor(tester, 2), isSameColorAs(CupertinoColors.activeBlue));
|
|
});
|
|
|
|
testWidgets('Segment is selected while it is transitioning to unselected state', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(setupSimpleSegmentedControl());
|
|
|
|
await tester.tap(find.text('Child 2'));
|
|
|
|
await tester.pump();
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.activeBlue));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(const Color(0x33007aff)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 40));
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(const Color(0xff3d9aff)));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(const Color(0x64007aff)));
|
|
|
|
// While A to B transition is occurring, press on A again.
|
|
await tester.tap(find.text('Child 1'));
|
|
|
|
await tester.pump();
|
|
|
|
// Both transitions start to reverse.
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(const Color(0xcd007aff)));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(const Color(0xffc1deff)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 40));
|
|
// A and B finish transitioning.
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.activeBlue));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white));
|
|
});
|
|
|
|
testWidgets('Add segment while animation is running', (WidgetTester tester) async {
|
|
var children = <int, Widget>{};
|
|
children[0] = const Text('A');
|
|
children[1] = const Text('B');
|
|
children[2] = const Text('C');
|
|
var sharedValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
key: const ValueKey<String>('Segmented Control'),
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
if (sharedValue == 1) {
|
|
children = Map<int, Widget>.of(children);
|
|
children[3] = const Text('D');
|
|
}
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('B'));
|
|
|
|
await tester.pump();
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.white));
|
|
expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
|
|
expect(getBackgroundColor(tester, 3), isSameColorAs(CupertinoColors.white));
|
|
|
|
await tester.pump(const Duration(milliseconds: 40));
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.white));
|
|
expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
|
|
expect(getBackgroundColor(tester, 3), isSameColorAs(CupertinoColors.white));
|
|
|
|
await tester.pump(const Duration(milliseconds: 150));
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.white));
|
|
expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
|
|
expect(getBackgroundColor(tester, 3), isSameColorAs(CupertinoColors.white));
|
|
});
|
|
|
|
testWidgets('Remove segment while animation is running', (WidgetTester tester) async {
|
|
var children = <int, Widget>{};
|
|
children[0] = const Text('A');
|
|
children[1] = const Text('B');
|
|
children[2] = const Text('C');
|
|
var sharedValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
key: const ValueKey<String>('Segmented Control'),
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
if (sharedValue == 1) {
|
|
children.remove(2);
|
|
children = Map<int, Widget>.of(children);
|
|
}
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(getChildCount(tester), 3);
|
|
|
|
await tester.tap(find.text('B'));
|
|
|
|
await tester.pump();
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(const Color(0x33007aff)));
|
|
expect(getChildCount(tester), 2);
|
|
|
|
await tester.pump(const Duration(milliseconds: 40));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(const Color(0x64007aff)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 150));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.activeBlue));
|
|
});
|
|
|
|
testWidgets('Remove currently animating segment', (WidgetTester tester) async {
|
|
var children = <int, Widget>{};
|
|
children[0] = const Text('A');
|
|
children[1] = const Text('B');
|
|
children[2] = const Text('C');
|
|
int? sharedValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
key: const ValueKey<String>('Segmented Control'),
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
if (sharedValue == 1) {
|
|
children.remove(1);
|
|
children = Map<int, Widget>.of(children);
|
|
sharedValue = null;
|
|
}
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(getChildCount(tester), 3);
|
|
|
|
await tester.tap(find.text('B'));
|
|
|
|
await tester.pump();
|
|
expect(getChildCount(tester), 2);
|
|
|
|
await tester.pump(const Duration(milliseconds: 40));
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(const Color(0xff3d9aff)));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white));
|
|
|
|
await tester.pump(const Duration(milliseconds: 40));
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(const Color(0xff7bbaff)));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white));
|
|
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.white));
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white));
|
|
});
|
|
|
|
// Regression test: https://github.com/flutter/flutter/issues/43414.
|
|
testWidgets("Quick double tap doesn't break the internal state", (WidgetTester tester) async {
|
|
const children = <int, Widget>{0: Text('A'), 1: Text('B'), 2: Text('C')};
|
|
var sharedValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
key: const ValueKey<String>('Segmented Control'),
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('B'));
|
|
// sharedValue has been updated but widget.groupValue is not.
|
|
expect(sharedValue, 1);
|
|
|
|
// Land the second tap before the widget gets a chance to rebuild.
|
|
final TestGesture secondTap = await tester.startGesture(tester.getCenter(find.text('B')));
|
|
await tester.pump();
|
|
|
|
await secondTap.up();
|
|
expect(sharedValue, 1);
|
|
|
|
await tester.tap(find.text('C'));
|
|
expect(sharedValue, 2);
|
|
});
|
|
|
|
testWidgets('Golden Test Placeholder Widget', (WidgetTester tester) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = Container();
|
|
children[1] = const Placeholder();
|
|
children[2] = Container();
|
|
|
|
const currentValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
RepaintBoundary(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: SizedBox(
|
|
width: 800.0,
|
|
child: CupertinoSegmentedControl<int>(
|
|
key: const ValueKey<String>('Segmented Control'),
|
|
children: children,
|
|
onValueChanged: (int newValue) {},
|
|
groupValue: currentValue,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
await expectLater(
|
|
find.byType(RepaintBoundary),
|
|
matchesGoldenFile('segmented_control_test.0.png'),
|
|
);
|
|
});
|
|
|
|
testWidgets('Golden Test Pressed State', (WidgetTester tester) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('A');
|
|
children[1] = const Text('B');
|
|
children[2] = const Text('C');
|
|
|
|
const currentValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
RepaintBoundary(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: SizedBox(
|
|
width: 800.0,
|
|
child: CupertinoSegmentedControl<int>(
|
|
key: const ValueKey<String>('Segmented Control'),
|
|
children: children,
|
|
onValueChanged: (int newValue) {},
|
|
groupValue: currentValue,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
final Offset center = tester.getCenter(find.text('B'));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
await expectLater(
|
|
find.byType(RepaintBoundary),
|
|
matchesGoldenFile('segmented_control_test.1.png'),
|
|
);
|
|
|
|
// Finish gesture to release resources.
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('Hovering over Cupertino segmented control updates cursor to clickable on Web', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('A');
|
|
children[1] = const Text('B');
|
|
children[2] = const Text('C');
|
|
|
|
const currentValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
RepaintBoundary(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: SizedBox(
|
|
width: 800.0,
|
|
child: CupertinoSegmentedControl<int>(
|
|
key: const ValueKey<String>('Segmented Control'),
|
|
children: children,
|
|
onValueChanged: (int newValue) {},
|
|
groupValue: currentValue,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
final TestGesture gesture = await tester.createGesture(
|
|
kind: PointerDeviceKind.mouse,
|
|
pointer: 1,
|
|
);
|
|
await gesture.addPointer(location: const Offset(10, 10));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
SystemMouseCursors.basic,
|
|
);
|
|
|
|
final Offset firstChild = tester.getCenter(find.text('A'));
|
|
await gesture.moveTo(firstChild);
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
kIsWeb ? SystemMouseCursors.click : SystemMouseCursors.basic,
|
|
);
|
|
});
|
|
|
|
testWidgets('Tap on disabled segment should not change its state', (WidgetTester tester) async {
|
|
final children = <int, Widget>{
|
|
0: const Text('Child 1'),
|
|
1: const Text('Child 2'),
|
|
2: const Text('Child 3'),
|
|
};
|
|
|
|
final disabledChildren = <int>{1};
|
|
|
|
var sharedValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
key: const ValueKey<String>('Segmented Control'),
|
|
children: children,
|
|
disabledChildren: disabledChildren,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(sharedValue, 0);
|
|
|
|
await tester.tap(find.text('Child 2'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(sharedValue, 0);
|
|
});
|
|
|
|
testWidgets('Background color of disabled segment should be different than enabled segment', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final children = <int, Widget>{0: const Text('Child 1'), 1: const Text('Child 2')};
|
|
var sharedValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
disabledChildren: const <int>{0},
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
// Colors are different for disabled and enabled segments in initial state.
|
|
// By default, the first segment is selected (and also is disabled in this test),
|
|
// it should have a blue background (selected color) with 50% opacity
|
|
expect(
|
|
getBackgroundColor(tester, 0),
|
|
isSameColorAs(CupertinoColors.systemBlue.withOpacity(0.5)),
|
|
);
|
|
expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white));
|
|
|
|
// Tap on disabled segment should not change its color
|
|
await tester.tap(find.text('Child 1'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
getBackgroundColor(tester, 0),
|
|
isSameColorAs(CupertinoColors.systemBlue.withOpacity(0.5)),
|
|
);
|
|
|
|
// When tapping on another enabled segment, the first disabled segment is not selected anymore,
|
|
// it should have a white background (same to unselected color).
|
|
await tester.tap(find.text('Child 2'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.white));
|
|
});
|
|
|
|
testWidgets('Custom disabled color of disabled segment is showing as desired', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final children = <int, Widget>{
|
|
0: const Text('Child 1'),
|
|
1: const Text('Child 2'),
|
|
2: const Text('Child 3'),
|
|
};
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
disabledChildren: const <int>{0},
|
|
onValueChanged: (int newValue) {},
|
|
disabledColor: CupertinoColors.systemGrey2,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.systemGrey2));
|
|
});
|
|
|
|
testWidgets('Segmented control can use arrow keys', (WidgetTester tester) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = const Text('Child 2');
|
|
children[2] = const Text('Child 3');
|
|
|
|
var sharedValue = 0;
|
|
final focusNode = FocusNode();
|
|
addTearDown(focusNode.dispose);
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(sharedValue, 0);
|
|
|
|
await tester.tap(find.text('Child 1'));
|
|
await tester.pumpAndSettle();
|
|
expect(sharedValue, 0);
|
|
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
|
await tester.pumpAndSettle();
|
|
expect(sharedValue, 1);
|
|
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
|
await tester.pumpAndSettle();
|
|
expect(sharedValue, 2);
|
|
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
|
await tester.pumpAndSettle();
|
|
expect(sharedValue, 0);
|
|
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
|
|
await tester.pumpAndSettle();
|
|
expect(sharedValue, 2);
|
|
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
|
await tester.pumpAndSettle();
|
|
expect(sharedValue, 1);
|
|
});
|
|
|
|
testWidgets('Segmented control skips disabled segments with keyboard', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final children = <int, Widget>{};
|
|
children[0] = const Text('Child 1');
|
|
children[1] = const Text('Child 2');
|
|
children[2] = const Text('Child 3');
|
|
|
|
var sharedValue = 0;
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoSegmentedControl<int>(
|
|
children: children,
|
|
onValueChanged: (int newValue) {
|
|
setState(() {
|
|
sharedValue = newValue;
|
|
});
|
|
},
|
|
groupValue: sharedValue,
|
|
disabledChildren: const <int>{1},
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(sharedValue, 0);
|
|
|
|
await tester.tap(find.text('Child 1'));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
|
await tester.pumpAndSettle();
|
|
expect(sharedValue, 2);
|
|
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
|
|
await tester.pumpAndSettle();
|
|
expect(sharedValue, 0);
|
|
});
|
|
|
|
testWidgets('CupertinoSegmentedControl does not crash at zero area', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox.shrink(
|
|
child: CupertinoSegmentedControl<int>(
|
|
onValueChanged: (_) {},
|
|
children: const <int, Widget>{1: Text('X'), 2: Text('Y')},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(tester.getSize(find.byType(CupertinoSegmentedControl<int>)), Size.zero);
|
|
});
|
|
}
|