| // 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_test/flutter_test.dart'; |
| |
| import '../widgets/semantics_tester.dart'; |
| |
| const TextStyle testStyle = TextStyle( |
| fontFamily: 'Ahem', |
| 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 + 16px * 2 is smaller than the default 44px minimum. |
| const Size.square(44.0), |
| ); |
| }); |
| |
| testWidgets('Minimum size parameter', (WidgetTester tester) async { |
| const double 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 + 16px * 2 is smaller than defined 60.0px minimum |
| const Size.square(minSize), |
| ); |
| }); |
| |
| 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 + 16px * 2 = 72. |
| 72.0, |
| ); |
| }); |
| |
| // 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 with background is wider', (WidgetTester tester) async { |
| await tester.pumpWidget(boilerplate(child: const CupertinoButton( |
| onPressed: null, |
| color: Color(0xFFFFFFFF), |
| child: Text('X', style: testStyle), |
| ))); |
| final RenderBox buttonBox = tester.renderObject(find.byType(CupertinoButton)); |
| expect( |
| buttonBox.size.width, |
| // 1 10px character + 64 * 2 = 138 for buttons with background. |
| 138.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 { |
| bool 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)); |
| 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); |
| }); |
| |
| testWidgets('pressedOpacity parameter', (WidgetTester tester) async { |
| const double 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)); |
| 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); |
| }); |
| |
| testWidgets('Cupertino button is semantically a button', (WidgetTester tester) async { |
| final SemanticsTester 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, |
| label: 'ABC', |
| flags: SemanticsFlag.isButton.index, |
| ), |
| ], |
| ), |
| 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'), |
| ))); |
| |
| BoxDecoration boxDecoration = tester.widget<DecoratedBox>( |
| find.widgetWithText(DecoratedBox, 'Skeuomorph me'), |
| ).decoration as BoxDecoration; |
| |
| expect(boxDecoration.color, const Color(0x000000FF)); |
| |
| await tester.pumpWidget(boilerplate(child: const CupertinoButton( |
| color: Color(0x000000FF), |
| disabledColor: Color(0x0000FF00), |
| onPressed: null, |
| child: Text('Skeuomorph me'), |
| ))); |
| |
| boxDecoration = tester.widget<DecoratedBox>( |
| find.widgetWithText(DecoratedBox, 'Skeuomorph me'), |
| ).decoration as BoxDecoration; |
| |
| expect(boxDecoration.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'), |
| )), |
| ), |
| ); |
| |
| BoxDecoration boxDecoration = tester.widget<DecoratedBox>( |
| find.widgetWithText(DecoratedBox, 'Skeuomorph me'), |
| ).decoration as BoxDecoration; |
| |
| expect(boxDecoration.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'), |
| )), |
| ), |
| ); |
| |
| boxDecoration = tester.widget<DecoratedBox>( |
| find.widgetWithText(DecoratedBox, 'Skeuomorph me'), |
| ).decoration as BoxDecoration; |
| |
| // Disabled color. |
| expect(boxDecoration.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, CupertinoColors.activeBlue); |
| |
| 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)); |
| BoxDecoration decoration = tester.widget<DecoratedBox>( |
| find.descendant( |
| of: find.byType(CupertinoButton), |
| matching: find.byType(DecoratedBox), |
| ), |
| ).decoration as BoxDecoration; |
| expect(decoration.color, 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.filled( |
| onPressed: () { }, |
| child: Builder(builder: (BuildContext context) { |
| textStyle = DefaultTextStyle.of(context).style; |
| return const Placeholder(); |
| }), |
| ), |
| ), |
| ); |
| expect(textStyle.color, isSameColorAs(CupertinoColors.black)); |
| decoration = tester.widget<DecoratedBox>( |
| find.descendant( |
| of: find.byType(CupertinoButton), |
| matching: find.byType(DecoratedBox), |
| ), |
| ).decoration as BoxDecoration; |
| expect(decoration.color, isSameColorAs(CupertinoColors.systemBlue.darkColor)); |
| }); |
| |
| 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, |
| ); |
| }); |
| } |
| |
| Widget boilerplate({ required Widget child }) { |
| return Directionality( |
| textDirection: TextDirection.ltr, |
| child: Center(child: child), |
| ); |
| } |