1169 lines
36 KiB
Dart
1169 lines
36 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.
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/scheduler.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import '../widgets/semantics_tester.dart';
|
|
|
|
const TextStyle testStyle = TextStyle(fontSize: 10.0, letterSpacing: 0.0);
|
|
|
|
void main() {
|
|
testWidgets('Default layout minimum size', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: const CupertinoButton(onPressed: null, child: Text('X', style: testStyle)),
|
|
),
|
|
);
|
|
final RenderBox buttonBox = tester.renderObject(find.byType(CupertinoButton));
|
|
expect(
|
|
buttonBox.size,
|
|
// 1 10px character + 20px * 2 = 50.0
|
|
const Size(50.0, 44.0),
|
|
);
|
|
});
|
|
|
|
testWidgets('Minimum size parameter', (WidgetTester tester) async {
|
|
const minSize = 60.0;
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: const CupertinoButton(
|
|
onPressed: null,
|
|
minSize: minSize,
|
|
child: Text('X', style: testStyle),
|
|
),
|
|
),
|
|
);
|
|
final RenderBox buttonBox = tester.renderObject(find.byType(CupertinoButton));
|
|
expect(
|
|
buttonBox.size,
|
|
// 1 10px character + 20px * 2 = 50.0 (is smaller than minSize: 60.0)
|
|
const Size.square(minSize),
|
|
);
|
|
});
|
|
|
|
testWidgets('Minimum size minimumSize parameter', (WidgetTester tester) async {
|
|
const size = Size(60.0, 100.0);
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: const CupertinoButton(onPressed: null, minimumSize: size, child: SizedBox.shrink()),
|
|
),
|
|
);
|
|
final RenderBox buttonBox = tester.renderObject(find.byType(CupertinoButton));
|
|
expect(buttonBox.size, size);
|
|
});
|
|
|
|
testWidgets('Size grows with text', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: const CupertinoButton(onPressed: null, child: Text('XXXX', style: testStyle)),
|
|
),
|
|
);
|
|
final RenderBox buttonBox = tester.renderObject(find.byType(CupertinoButton));
|
|
expect(
|
|
buttonBox.size.width,
|
|
// 4 10px character + 20px * 2 = 80.0
|
|
80.0,
|
|
);
|
|
});
|
|
|
|
testWidgets('OnLongPress works!', (WidgetTester tester) async {
|
|
var value = false;
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: CupertinoButton(
|
|
onPressed: null,
|
|
onLongPress: () {
|
|
value = !value;
|
|
},
|
|
child: const Text('XXXX', style: testStyle),
|
|
),
|
|
),
|
|
);
|
|
await tester.pump();
|
|
final Finder cupertinoBtn = find.byType(CupertinoButton);
|
|
await tester.longPress(cupertinoBtn);
|
|
expect(value, isTrue);
|
|
});
|
|
|
|
testWidgets('button is disabled if onLongPress and onPressed are both null', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: const CupertinoButton(onPressed: null, child: Text('XXXX', style: testStyle)),
|
|
),
|
|
);
|
|
|
|
expect(find.byType(CupertinoButton), findsOneWidget);
|
|
final CupertinoButton button = tester.widget(find.byType(CupertinoButton));
|
|
expect(button.enabled, isFalse);
|
|
});
|
|
|
|
// TODO(LongCatIsLoong): Uncomment once https://github.com/flutter/flutter/issues/44115
|
|
// is fixed.
|
|
/*
|
|
testWidgets(
|
|
'CupertinoButton.filled default color contrast meets guideline',
|
|
(WidgetTester tester) async {
|
|
// The native color combination systemBlue text over white background fails
|
|
// to pass the color contrast guideline.
|
|
//await tester.pumpWidget(
|
|
// CupertinoTheme(
|
|
// data: const CupertinoThemeData(),
|
|
// child: Directionality(
|
|
// textDirection: TextDirection.ltr,
|
|
// child: CupertinoButton.filled(
|
|
// child: const Text('Button'),
|
|
// onPressed: () {},
|
|
// ),
|
|
// ),
|
|
// ),
|
|
//);
|
|
//await expectLater(tester, meetsGuideline(textContrastGuideline));
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
theme: const CupertinoThemeData(brightness: Brightness.dark),
|
|
home: CupertinoPageScaffold(
|
|
child: CupertinoButton.filled(
|
|
child: const Text('Button'),
|
|
onPressed: () {},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
|
});
|
|
*/
|
|
|
|
testWidgets('Button child alignment', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoButton(onPressed: () {}, child: const Text('button')),
|
|
),
|
|
);
|
|
|
|
Align align = tester.firstWidget<Align>(
|
|
find.ancestor(of: find.text('button'), matching: find.byType(Align)),
|
|
);
|
|
expect(align.alignment, Alignment.center); // default
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoButton(
|
|
alignment: Alignment.centerLeft,
|
|
onPressed: () {},
|
|
child: const Text('button'),
|
|
),
|
|
),
|
|
);
|
|
|
|
align = tester.firstWidget<Align>(
|
|
find.ancestor(of: find.text('button'), matching: find.byType(Align)),
|
|
);
|
|
expect(align.alignment, Alignment.centerLeft);
|
|
});
|
|
|
|
testWidgets('Button size changes depending on size property', (WidgetTester tester) async {
|
|
const Widget child = Text('X', style: testStyle);
|
|
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: const CupertinoButton(
|
|
onPressed: null,
|
|
sizeStyle: CupertinoButtonSize.small,
|
|
child: child,
|
|
),
|
|
),
|
|
);
|
|
final RenderBox buttonBox = tester.renderObject(find.byType(CupertinoButton));
|
|
expect(buttonBox.size, const Size(34.0, 28.0));
|
|
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: const CupertinoButton(
|
|
onPressed: null,
|
|
sizeStyle: CupertinoButtonSize.medium,
|
|
child: child,
|
|
),
|
|
),
|
|
);
|
|
expect(buttonBox.size, const Size(40.0, 32.0));
|
|
|
|
await tester.pumpWidget(
|
|
boilerplate(child: const CupertinoButton(onPressed: null, child: child)),
|
|
);
|
|
expect(buttonBox.size, const Size(50.0, 44.0));
|
|
});
|
|
|
|
testWidgets('Custom padding', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: const CupertinoButton(
|
|
onPressed: null,
|
|
padding: EdgeInsets.all(100.0),
|
|
child: Text('X', style: testStyle),
|
|
),
|
|
),
|
|
);
|
|
final RenderBox buttonBox = tester.renderObject(find.byType(CupertinoButton));
|
|
expect(buttonBox.size, const Size.square(210.0));
|
|
});
|
|
|
|
testWidgets('Button takes taps', (WidgetTester tester) async {
|
|
var value = false;
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return boilerplate(
|
|
child: CupertinoButton(
|
|
child: const Text('Tap me'),
|
|
onPressed: () {
|
|
setState(() {
|
|
value = true;
|
|
});
|
|
},
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(value, isFalse);
|
|
// No animating by default.
|
|
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
|
|
await tester.tap(find.byType(CupertinoButton));
|
|
expect(value, isTrue);
|
|
// Animates.
|
|
expect(SchedulerBinding.instance.transientCallbackCount, equals(1));
|
|
});
|
|
|
|
testWidgets("Disabled button doesn't animate", (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
boilerplate(child: const CupertinoButton(onPressed: null, child: Text('Tap me'))),
|
|
);
|
|
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
|
|
await tester.tap(find.byType(CupertinoButton));
|
|
// Still doesn't animate.
|
|
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
|
|
});
|
|
|
|
testWidgets('Enabled button animates', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: CupertinoButton(child: const Text('Tap me'), onPressed: () {}),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.byType(CupertinoButton));
|
|
// Enter animation.
|
|
await tester.pump();
|
|
FadeTransition transition = tester.firstWidget(find.byType(FadeTransition));
|
|
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
transition = tester.firstWidget(find.byType(FadeTransition));
|
|
expect(transition.opacity.value, moreOrLessEquals(0.403, epsilon: 0.001));
|
|
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
transition = tester.firstWidget(find.byType(FadeTransition));
|
|
expect(transition.opacity.value, moreOrLessEquals(0.400, epsilon: 0.001));
|
|
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
transition = tester.firstWidget(find.byType(FadeTransition));
|
|
expect(transition.opacity.value, moreOrLessEquals(0.650, epsilon: 0.001));
|
|
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
transition = tester.firstWidget(find.byType(FadeTransition));
|
|
expect(transition.opacity.value, moreOrLessEquals(0.894, epsilon: 0.001));
|
|
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
transition = tester.firstWidget(find.byType(FadeTransition));
|
|
expect(transition.opacity.value, moreOrLessEquals(0.988, epsilon: 0.001));
|
|
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
transition = tester.firstWidget(find.byType(FadeTransition));
|
|
expect(transition.opacity.value, moreOrLessEquals(1.0, epsilon: 0.001));
|
|
});
|
|
|
|
testWidgets('pressedOpacity defaults to 0.1', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: CupertinoButton(child: const Text('Tap me'), onPressed: () {}),
|
|
),
|
|
);
|
|
|
|
// Keep a "down" gesture on the button
|
|
final Offset center = tester.getCenter(find.byType(CupertinoButton));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Check opacity
|
|
final FadeTransition opacity = tester.widget(
|
|
find.descendant(of: find.byType(CupertinoButton), matching: find.byType(FadeTransition)),
|
|
);
|
|
expect(opacity.opacity.value, 0.4);
|
|
|
|
// Finish gesture to release resources.
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('pressedOpacity parameter', (WidgetTester tester) async {
|
|
const pressedOpacity = 0.5;
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: CupertinoButton(
|
|
pressedOpacity: pressedOpacity,
|
|
child: const Text('Tap me'),
|
|
onPressed: () {},
|
|
),
|
|
),
|
|
);
|
|
|
|
// Keep a "down" gesture on the button
|
|
final Offset center = tester.getCenter(find.byType(CupertinoButton));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Check opacity
|
|
final FadeTransition opacity = tester.widget(
|
|
find.descendant(of: find.byType(CupertinoButton), matching: find.byType(FadeTransition)),
|
|
);
|
|
expect(opacity.opacity.value, pressedOpacity);
|
|
|
|
// Finish gesture to release resources.
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('Cupertino button is semantically a button', (WidgetTester tester) async {
|
|
final semantics = SemanticsTester(tester);
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: Center(
|
|
child: CupertinoButton(onPressed: () {}, child: const Text('ABC')),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
semantics,
|
|
hasSemantics(
|
|
TestSemantics.root(
|
|
children: <TestSemantics>[
|
|
TestSemantics.rootChild(
|
|
actions: SemanticsAction.tap.index | SemanticsAction.focus.index,
|
|
label: 'ABC',
|
|
flags: <SemanticsFlag>[SemanticsFlag.isButton, SemanticsFlag.isFocusable],
|
|
),
|
|
],
|
|
),
|
|
ignoreId: true,
|
|
ignoreRect: true,
|
|
ignoreTransform: true,
|
|
),
|
|
);
|
|
|
|
semantics.dispose();
|
|
});
|
|
|
|
testWidgets('Can specify colors', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: CupertinoButton(
|
|
color: const Color(0x000000FF),
|
|
disabledColor: const Color(0x0000FF00),
|
|
onPressed: () {},
|
|
child: const Text('Skeuomorph me'),
|
|
),
|
|
),
|
|
);
|
|
|
|
var decoration =
|
|
tester.widget<DecoratedBox>(find.widgetWithText(DecoratedBox, 'Skeuomorph me')).decoration
|
|
as ShapeDecoration;
|
|
|
|
expect(decoration.color, const Color(0x000000FF));
|
|
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: const CupertinoButton(
|
|
color: Color(0x000000FF),
|
|
disabledColor: Color(0x0000FF00),
|
|
onPressed: null,
|
|
child: Text('Skeuomorph me'),
|
|
),
|
|
),
|
|
);
|
|
|
|
decoration =
|
|
tester.widget<DecoratedBox>(find.widgetWithText(DecoratedBox, 'Skeuomorph me')).decoration
|
|
as ShapeDecoration;
|
|
|
|
expect(decoration.color, const Color(0x0000FF00));
|
|
});
|
|
|
|
testWidgets('Can specify dynamic colors', (WidgetTester tester) async {
|
|
const Color bgColor = CupertinoDynamicColor.withBrightness(
|
|
color: Color(0xFF123456),
|
|
darkColor: Color(0xFF654321),
|
|
);
|
|
|
|
const Color inactive = CupertinoDynamicColor.withBrightness(
|
|
color: Color(0xFF111111),
|
|
darkColor: Color(0xFF222222),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
MediaQuery(
|
|
data: const MediaQueryData(platformBrightness: Brightness.dark),
|
|
child: boilerplate(
|
|
child: CupertinoButton(
|
|
color: bgColor,
|
|
disabledColor: inactive,
|
|
onPressed: () {},
|
|
child: const Text('Skeuomorph me'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
var decoration =
|
|
tester.widget<DecoratedBox>(find.widgetWithText(DecoratedBox, 'Skeuomorph me')).decoration
|
|
as ShapeDecoration;
|
|
|
|
expect(decoration.color!.value, 0xFF654321);
|
|
|
|
await tester.pumpWidget(
|
|
MediaQuery(
|
|
data: const MediaQueryData(),
|
|
child: boilerplate(
|
|
child: const CupertinoButton(
|
|
color: bgColor,
|
|
disabledColor: inactive,
|
|
onPressed: null,
|
|
child: Text('Skeuomorph me'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
decoration =
|
|
tester.widget<DecoratedBox>(find.widgetWithText(DecoratedBox, 'Skeuomorph me')).decoration
|
|
as ShapeDecoration;
|
|
|
|
// Disabled color.
|
|
expect(decoration.color!.value, 0xFF111111);
|
|
});
|
|
|
|
testWidgets('Button respects themes', (WidgetTester tester) async {
|
|
late TextStyle textStyle;
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoButton(
|
|
onPressed: () {},
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
textStyle = DefaultTextStyle.of(context).style;
|
|
return const Placeholder();
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(textStyle.color, isSameColorAs(CupertinoColors.activeBlue));
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoButton.tinted(
|
|
onPressed: () {},
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
textStyle = DefaultTextStyle.of(context).style;
|
|
return const Placeholder();
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(textStyle.color, CupertinoColors.activeBlue);
|
|
var decoration =
|
|
tester
|
|
.widget<DecoratedBox>(
|
|
find.descendant(
|
|
of: find.byType(CupertinoButton),
|
|
matching: find.byType(DecoratedBox),
|
|
),
|
|
)
|
|
.decoration
|
|
as ShapeDecoration;
|
|
expect(decoration.color, isSameColorAs(CupertinoColors.activeBlue.withOpacity(0.12)));
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoButton.filled(
|
|
onPressed: () {},
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
textStyle = DefaultTextStyle.of(context).style;
|
|
return const Placeholder();
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(textStyle.color, isSameColorAs(CupertinoColors.white));
|
|
decoration =
|
|
tester
|
|
.widget<DecoratedBox>(
|
|
find.descendant(
|
|
of: find.byType(CupertinoButton),
|
|
matching: find.byType(DecoratedBox),
|
|
),
|
|
)
|
|
.decoration
|
|
as ShapeDecoration;
|
|
expect(decoration.color, isSameColorAs(CupertinoColors.activeBlue));
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
theme: const CupertinoThemeData(brightness: Brightness.dark),
|
|
home: CupertinoButton(
|
|
onPressed: () {},
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
textStyle = DefaultTextStyle.of(context).style;
|
|
return const Placeholder();
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(textStyle.color, isSameColorAs(CupertinoColors.systemBlue.darkColor));
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
theme: const CupertinoThemeData(brightness: Brightness.dark),
|
|
home: CupertinoButton.tinted(
|
|
onPressed: () {},
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
textStyle = DefaultTextStyle.of(context).style;
|
|
return const Placeholder();
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(textStyle.color, isSameColorAs(CupertinoColors.systemBlue.darkColor));
|
|
decoration =
|
|
tester
|
|
.widget<DecoratedBox>(
|
|
find.descendant(
|
|
of: find.byType(CupertinoButton),
|
|
matching: find.byType(DecoratedBox),
|
|
),
|
|
)
|
|
.decoration
|
|
as ShapeDecoration;
|
|
expect(decoration.color, isSameColorAs(CupertinoColors.activeBlue.darkColor.withOpacity(0.26)));
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
theme: const CupertinoThemeData(brightness: Brightness.dark),
|
|
home: CupertinoButton.filled(
|
|
onPressed: () {},
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
textStyle = DefaultTextStyle.of(context).style;
|
|
return const Placeholder();
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(textStyle.color, isSameColorAs(CupertinoColors.white));
|
|
decoration =
|
|
tester
|
|
.widget<DecoratedBox>(
|
|
find.descendant(
|
|
of: find.byType(CupertinoButton),
|
|
matching: find.byType(DecoratedBox),
|
|
),
|
|
)
|
|
.decoration
|
|
as ShapeDecoration;
|
|
expect(decoration.color, isSameColorAs(CupertinoColors.systemBlue.darkColor));
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoButton.filled(
|
|
color: CupertinoColors.systemRed,
|
|
onPressed: () {},
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
textStyle = DefaultTextStyle.of(context).style;
|
|
return const Placeholder();
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
decoration =
|
|
tester
|
|
.widget<DecoratedBox>(
|
|
find.descendant(
|
|
of: find.byType(CupertinoButton),
|
|
matching: find.byType(DecoratedBox),
|
|
),
|
|
)
|
|
.decoration
|
|
as ShapeDecoration;
|
|
expect(decoration.color, isSameColorAs(CupertinoColors.systemRed));
|
|
});
|
|
|
|
testWidgets("All CupertinoButton const maps keys' match the available style sizes", (
|
|
WidgetTester tester,
|
|
) async {
|
|
for (final CupertinoButtonSize size in CupertinoButtonSize.values) {
|
|
expect(kCupertinoButtonPadding[size], isNotNull);
|
|
expect(kCupertinoButtonSizeBorderRadius[size], isNotNull);
|
|
expect(kCupertinoButtonMinSize[size], isNotNull);
|
|
}
|
|
});
|
|
|
|
testWidgets('Hovering over Cupertino button updates cursor to clickable on Web', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoButton.filled(onPressed: () {}, child: const Text('Tap me')),
|
|
),
|
|
),
|
|
);
|
|
|
|
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 button = tester.getCenter(find.byType(CupertinoButton));
|
|
await gesture.moveTo(button);
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
kIsWeb ? SystemMouseCursors.click : SystemMouseCursors.basic,
|
|
);
|
|
});
|
|
|
|
testWidgets('Button can be focused and has default colors', (WidgetTester tester) async {
|
|
final focusNode = FocusNode(debugLabel: 'Button');
|
|
addTearDown(focusNode.dispose);
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
final defaultFocusBorder = BorderSide(
|
|
color: HSLColor.fromColor(CupertinoColors.activeBlue.withOpacity(kCupertinoFocusColorOpacity))
|
|
.withLightness(kCupertinoFocusColorBrightness)
|
|
.withSaturation(kCupertinoFocusColorSaturation)
|
|
.toColor(),
|
|
width: 3.5,
|
|
strokeAlign: BorderSide.strokeAlignOutside,
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoButton(
|
|
onPressed: () {},
|
|
focusNode: focusNode,
|
|
autofocus: true,
|
|
child: const Text('Tap me'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
|
|
// The button has no border.
|
|
expect(
|
|
_findBorder(
|
|
tester,
|
|
find.descendant(of: find.byType(CupertinoButton), matching: find.byType(DecoratedBox)),
|
|
),
|
|
BorderSide.none,
|
|
);
|
|
await tester.pump();
|
|
|
|
// When focused, the button has a light blue border outline by default.
|
|
focusNode.requestFocus();
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
_findBorder(
|
|
tester,
|
|
find.descendant(of: find.byType(CupertinoButton), matching: find.byType(DecoratedBox)),
|
|
),
|
|
defaultFocusBorder,
|
|
);
|
|
});
|
|
|
|
testWidgets('Button configures focus color', (WidgetTester tester) async {
|
|
final focusNode = FocusNode(debugLabel: 'Button');
|
|
addTearDown(focusNode.dispose);
|
|
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
const Color focusColor = CupertinoColors.systemGreen;
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoButton(
|
|
onPressed: () {},
|
|
focusNode: focusNode,
|
|
autofocus: true,
|
|
focusColor: focusColor,
|
|
child: const Text('Tap me'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
focusNode.requestFocus();
|
|
await tester.pump();
|
|
await tester.pumpAndSettle();
|
|
final BorderSide borderSide = _findBorder(
|
|
tester,
|
|
find.descendant(of: find.byType(CupertinoButton), matching: find.byType(DecoratedBox)),
|
|
);
|
|
expect(borderSide.color, focusColor);
|
|
});
|
|
|
|
testWidgets('CupertinoButton.onFocusChange callback', (WidgetTester tester) async {
|
|
final focusNode = FocusNode(debugLabel: 'CupertinoButton');
|
|
addTearDown(focusNode.dispose);
|
|
|
|
var focused = false;
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoButton(
|
|
onPressed: () {},
|
|
focusNode: focusNode,
|
|
onFocusChange: (bool value) {
|
|
focused = value;
|
|
},
|
|
child: const Text('Tap me'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
focusNode.requestFocus();
|
|
await tester.pump();
|
|
expect(focused, isTrue);
|
|
expect(focusNode.hasFocus, isTrue);
|
|
|
|
focusNode.unfocus();
|
|
await tester.pump();
|
|
expect(focused, isFalse);
|
|
expect(focusNode.hasFocus, isFalse);
|
|
});
|
|
|
|
testWidgets('IconThemeData falls back to default value when the TextStyle has a null size', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const defaultIconTheme = IconThemeData(size: kCupertinoButtonDefaultIconSize);
|
|
|
|
IconThemeData? actualIconTheme;
|
|
|
|
// Large size.
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
theme: const CupertinoThemeData(
|
|
textTheme: CupertinoTextThemeData(actionTextStyle: TextStyle()),
|
|
),
|
|
home: Center(
|
|
child: CupertinoButton(
|
|
onPressed: () {},
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
actualIconTheme = IconTheme.of(context);
|
|
|
|
return const Placeholder();
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(actualIconTheme?.size, defaultIconTheme.size);
|
|
|
|
// Small size.
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
theme: const CupertinoThemeData(
|
|
textTheme: CupertinoTextThemeData(actionSmallTextStyle: TextStyle()),
|
|
),
|
|
home: Center(
|
|
child: CupertinoButton(
|
|
onPressed: () {},
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
actualIconTheme = IconTheme.of(context);
|
|
|
|
return const Placeholder();
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Button can be activated by keyboard shortcuts', (WidgetTester tester) async {
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
var value = true;
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return CupertinoButton(
|
|
onPressed: () {
|
|
setState(() {
|
|
value = !value;
|
|
});
|
|
},
|
|
autofocus: true,
|
|
child: const Text('Tap me'),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pump();
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
|
|
await tester.pump();
|
|
// On web, buttons don't respond to the enter key.
|
|
expect(value, kIsWeb ? isTrue : isFalse);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
|
|
await tester.pump();
|
|
expect(value, isTrue);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.space);
|
|
await tester.pump();
|
|
expect(value, isFalse);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.space);
|
|
await tester.pump();
|
|
expect(value, isTrue);
|
|
});
|
|
|
|
testWidgets('Press and move on button and animation works', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: CupertinoButton(onPressed: () {}, child: const Text('Tap me')),
|
|
),
|
|
);
|
|
final TestGesture gesture = await tester.startGesture(
|
|
tester.getTopLeft(find.byType(CupertinoButton)),
|
|
);
|
|
addTearDown(gesture.removePointer);
|
|
// Check opacity.
|
|
final FadeTransition opacity = tester.widget(
|
|
find.descendant(of: find.byType(CupertinoButton), matching: find.byType(FadeTransition)),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(opacity.opacity.value, 0.4);
|
|
final double moveDistance = CupertinoButton.tapMoveSlop();
|
|
await gesture.moveBy(Offset(0, -moveDistance + 1));
|
|
await tester.pumpAndSettle();
|
|
expect(opacity.opacity.value, 0.4);
|
|
await gesture.moveBy(const Offset(0, -2));
|
|
await tester.pumpAndSettle();
|
|
expect(opacity.opacity.value, 1.0);
|
|
await gesture.moveBy(const Offset(0, 1));
|
|
await tester.pumpAndSettle();
|
|
expect(opacity.opacity.value, 0.4);
|
|
}, variant: TargetPlatformVariant.all());
|
|
|
|
testWidgets('Drag outside button within ListView does not leave the button pressed', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: ListView(
|
|
children: <Widget>[CupertinoButton(onPressed: () {}, child: const Text('Tap me'))],
|
|
),
|
|
),
|
|
);
|
|
final FadeTransition opacity = tester.widget(
|
|
find.descendant(of: find.byType(CupertinoButton), matching: find.byType(FadeTransition)),
|
|
);
|
|
|
|
final TestGesture gesture = await tester.createGesture();
|
|
addTearDown(gesture.removePointer);
|
|
|
|
await gesture.down(tester.getTopLeft(find.byType(CupertinoButton)));
|
|
await gesture.moveBy(const Offset(1, 1));
|
|
await gesture.moveBy(Offset(0, -CupertinoButton.tapMoveSlop() - 5));
|
|
await tester.pumpAndSettle();
|
|
expect(opacity.opacity.value, 1.0);
|
|
});
|
|
|
|
testWidgets('onPressed trigger takes into account MoveSlop.', (WidgetTester tester) async {
|
|
var value = false;
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: CupertinoButton(
|
|
onPressed: () {
|
|
value = true;
|
|
},
|
|
child: const Text('Tap me'),
|
|
),
|
|
),
|
|
);
|
|
TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CupertinoButton)));
|
|
await gesture.moveTo(
|
|
tester.getBottomRight(find.byType(CupertinoButton)) +
|
|
Offset(0, CupertinoButton.tapMoveSlop()),
|
|
);
|
|
await gesture.up();
|
|
expect(value, isFalse);
|
|
|
|
gesture = await tester.startGesture(tester.getTopLeft(find.byType(CupertinoButton)));
|
|
await gesture.moveTo(
|
|
tester.getBottomRight(find.byType(CupertinoButton)) +
|
|
Offset(0, CupertinoButton.tapMoveSlop()),
|
|
);
|
|
await gesture.moveBy(const Offset(0, -1));
|
|
await gesture.up();
|
|
expect(value, isTrue);
|
|
});
|
|
|
|
testWidgets('Mouse cursor resolves in enabled/disabled/pressed/focused states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final focusNode = FocusNode(debugLabel: 'Button');
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
addTearDown(focusNode.dispose);
|
|
Widget buildButton({required bool enabled, MouseCursor? cursor}) {
|
|
return CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoButton(
|
|
focusNode: focusNode,
|
|
onPressed: enabled ? () {} : null,
|
|
mouseCursor: cursor,
|
|
child: const Text('Tap Me'),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Test default mouse cursor
|
|
final TestGesture gesture = await tester.createGesture(
|
|
kind: PointerDeviceKind.mouse,
|
|
pointer: 1,
|
|
);
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pumpWidget(buildButton(enabled: true, cursor: const _ButtonMouseCursor()));
|
|
await gesture.addPointer(location: tester.getCenter(find.byType(CupertinoButton)));
|
|
await tester.pump();
|
|
await gesture.moveTo(tester.getCenter(find.byType(CupertinoButton)));
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
SystemMouseCursors.basic,
|
|
);
|
|
|
|
// Test disabled state mouse cursor
|
|
await tester.pumpWidget(buildButton(enabled: false, cursor: const _ButtonMouseCursor()));
|
|
await gesture.moveTo(tester.getCenter(find.byType(CupertinoButton)));
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
SystemMouseCursors.forbidden,
|
|
);
|
|
|
|
// Test focused state mouse cursor
|
|
await tester.pumpWidget(buildButton(enabled: true, cursor: const _ButtonMouseCursor()));
|
|
focusNode.requestFocus();
|
|
await tester.pump();
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
SystemMouseCursors.copy,
|
|
);
|
|
focusNode.unfocus();
|
|
|
|
// Test pressed state mouse cursor
|
|
await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.down(tester.getCenter(find.byType(CupertinoButton)));
|
|
await tester.pump();
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
SystemMouseCursors.grab,
|
|
);
|
|
await gesture.up();
|
|
await gesture.removePointer();
|
|
});
|
|
|
|
testWidgets('CupertinoButton foregroundColor applies to its text', (WidgetTester tester) async {
|
|
const customForegroundColor = Color(0xFF5500FF);
|
|
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: CupertinoButton(
|
|
onPressed: () {},
|
|
foregroundColor: customForegroundColor,
|
|
child: const Text('Button'),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Check that the text has the custom foreground color
|
|
final RichText text = tester.widget(
|
|
find.descendant(of: find.byType(CupertinoButton), matching: find.byType(RichText)),
|
|
);
|
|
expect(text.text.style?.color, customForegroundColor);
|
|
});
|
|
|
|
testWidgets('CupertinoButton foregroundColor applies to its icon', (WidgetTester tester) async {
|
|
const customForegroundColor = Color(0xFF5500FF);
|
|
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: CupertinoButton(
|
|
onPressed: () {},
|
|
foregroundColor: customForegroundColor,
|
|
child: const Icon(IconData(0xE000)),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Check that the icon has the custom foreground color
|
|
final IconTheme iconTheme = tester.widget(
|
|
find.descendant(of: find.byType(CupertinoButton), matching: find.byType(IconTheme)),
|
|
);
|
|
expect(iconTheme.data.color, customForegroundColor);
|
|
});
|
|
|
|
testWidgets(
|
|
"CupertinoButton uses the theme's primaryColor when foregroundColor is not specified",
|
|
(WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoButton(onPressed: () {}, child: const Text('Button')),
|
|
),
|
|
),
|
|
);
|
|
|
|
// The default color should be the primary color from the theme
|
|
final BuildContext context = tester.element(find.text('Button'));
|
|
final Color primaryColor = CupertinoTheme.of(context).primaryColor;
|
|
|
|
final RichText text = tester.widget(find.byType(RichText));
|
|
expect(text.text.style?.color, primaryColor);
|
|
},
|
|
);
|
|
|
|
testWidgets('CupertinoButton.filled foregroundColor applies to its text', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const customForegroundColor = Color(0xFF5500FF);
|
|
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: CupertinoButton.filled(
|
|
onPressed: () {},
|
|
foregroundColor: customForegroundColor,
|
|
child: const Text('Button'),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Check that the text has the custom foreground color
|
|
final RichText text = tester.widget(
|
|
find.descendant(of: find.byType(CupertinoButton), matching: find.byType(RichText)),
|
|
);
|
|
expect(text.text.style?.color, customForegroundColor);
|
|
});
|
|
|
|
testWidgets('CupertinoButton foregroundColor applies to its text when disabled', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const customForegroundColor = Color(0xFF5500FF);
|
|
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
child: const CupertinoButton(
|
|
onPressed: null, // disabled button
|
|
foregroundColor: customForegroundColor,
|
|
child: Text('Button'),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Check that the text has the custom foreground color even when disabled
|
|
final RichText text = tester.widget(
|
|
find.descendant(of: find.byType(CupertinoButton), matching: find.byType(RichText)),
|
|
);
|
|
expect(text.text.style?.color, customForegroundColor);
|
|
});
|
|
|
|
testWidgets('CupertinoButton does not crash at zero area', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox.shrink(
|
|
child: CupertinoButton(child: const Text('X'), onPressed: () {}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(tester.getSize(find.byType(CupertinoButton)), Size.zero);
|
|
});
|
|
}
|
|
|
|
Widget boilerplate({required Widget child}) {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(child: child),
|
|
);
|
|
}
|
|
|
|
class _ButtonMouseCursor extends WidgetStateMouseCursor {
|
|
const _ButtonMouseCursor();
|
|
|
|
@override
|
|
MouseCursor resolve(Set<WidgetState> states) {
|
|
return const WidgetStateProperty<MouseCursor>.fromMap(<WidgetStatesConstraint, MouseCursor>{
|
|
WidgetState.disabled: SystemMouseCursors.forbidden,
|
|
WidgetState.pressed: SystemMouseCursors.grab,
|
|
WidgetState.focused: SystemMouseCursors.copy,
|
|
WidgetState.any: SystemMouseCursors.basic,
|
|
}).resolve(states);
|
|
}
|
|
|
|
@override
|
|
String get debugDescription => '_ButtonMouseCursor()';
|
|
}
|
|
|
|
BorderSide _findBorder(WidgetTester tester, Finder finder) {
|
|
final decoration = tester.widget<DecoratedBox>(finder).decoration as ShapeDecoration;
|
|
return (decoration.shape as RoundedSuperellipseBorder).side;
|
|
}
|