blob: c37343f53034d169a4955703ba77952c74341b10 [file] [log] [blame] [edit]
// 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:async';
import 'dart:ui' as ui;
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import '../widgets/semantics_tester.dart';
void main() {
testWidgets('Switch can toggle on tap', (WidgetTester tester) async {
final Key switchKey = UniqueKey();
bool value = false;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: CupertinoSwitch(
key: switchKey,
value: value,
dragStartBehavior: DragStartBehavior.down,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
);
},
),
),
);
expect(value, isFalse);
await tester.tap(find.byKey(switchKey));
expect(value, isTrue);
});
testWidgets('CupertinoSwitch can be toggled by keyboard shortcuts', (WidgetTester tester) async {
bool value = true;
Widget buildApp({bool enabled = true}) {
return CupertinoApp(
home: CupertinoPageScaffold(
child: Center(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return CupertinoSwitch(
value: value,
onChanged:
enabled
? (bool newValue) {
setState(() {
value = newValue;
});
}
: null,
);
},
),
),
),
);
}
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(value, isTrue);
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
await tester.pumpAndSettle();
await tester.sendKeyEvent(LogicalKeyboardKey.space);
await tester.pumpAndSettle();
expect(value, isFalse);
await tester.sendKeyEvent(LogicalKeyboardKey.space);
await tester.pumpAndSettle();
expect(value, isTrue);
});
testWidgets(
'Switch emits light haptic vibration on tap',
(WidgetTester tester) async {
final Key switchKey = UniqueKey();
bool value = false;
final List<MethodCall> log = <MethodCall>[];
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (
MethodCall methodCall,
) async {
log.add(methodCall);
return null;
});
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: CupertinoSwitch(
key: switchKey,
value: value,
dragStartBehavior: DragStartBehavior.down,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
);
},
),
),
);
await tester.tap(find.byKey(switchKey));
await tester.pump();
expect(log, hasLength(1));
expect(
log.single,
isMethodCall('HapticFeedback.vibrate', arguments: 'HapticFeedbackType.lightImpact'),
);
},
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
);
testWidgets(
'Using other widgets that rebuild the switch will not cause vibrations',
(WidgetTester tester) async {
final Key switchKey = UniqueKey();
final Key switchKey2 = UniqueKey();
bool value = false;
bool value2 = false;
final List<MethodCall> log = <MethodCall>[];
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (
MethodCall methodCall,
) async {
log.add(methodCall);
return null;
});
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: Column(
children: <Widget>[
CupertinoSwitch(
key: switchKey,
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
CupertinoSwitch(
key: switchKey2,
value: value2,
onChanged: (bool newValue) {
setState(() {
value2 = newValue;
});
},
),
],
),
);
},
),
),
);
await tester.tap(find.byKey(switchKey));
await tester.pump();
expect(log, hasLength(1));
expect(
log[0],
isMethodCall('HapticFeedback.vibrate', arguments: 'HapticFeedbackType.lightImpact'),
);
await tester.tap(find.byKey(switchKey2));
await tester.pump();
expect(log, hasLength(2));
expect(
log[1],
isMethodCall('HapticFeedback.vibrate', arguments: 'HapticFeedbackType.lightImpact'),
);
await tester.tap(find.byKey(switchKey));
await tester.pump();
expect(log, hasLength(3));
expect(
log[2],
isMethodCall('HapticFeedback.vibrate', arguments: 'HapticFeedbackType.lightImpact'),
);
await tester.tap(find.byKey(switchKey2));
await tester.pump();
expect(log, hasLength(4));
expect(
log[3],
isMethodCall('HapticFeedback.vibrate', arguments: 'HapticFeedbackType.lightImpact'),
);
},
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
);
testWidgets('Haptic vibration triggers on drag', (WidgetTester tester) async {
bool value = false;
final List<MethodCall> log = <MethodCall>[];
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (
MethodCall methodCall,
) async {
log.add(methodCall);
return null;
});
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: CupertinoSwitch(
value: value,
dragStartBehavior: DragStartBehavior.down,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
);
},
),
),
);
await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0));
expect(value, isTrue);
await tester.pump();
expect(log, hasLength(1));
expect(
log[0],
isMethodCall('HapticFeedback.vibrate', arguments: 'HapticFeedbackType.lightImpact'),
);
}, variant: TargetPlatformVariant.only(TargetPlatform.iOS));
testWidgets(
'No haptic vibration triggers from a programmatic value change',
(WidgetTester tester) async {
final Key switchKey = UniqueKey();
bool value = false;
final List<MethodCall> log = <MethodCall>[];
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (
MethodCall methodCall,
) async {
log.add(methodCall);
return null;
});
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: Column(
children: <Widget>[
CupertinoButton(
child: const Text('Button'),
onPressed: () {
setState(() {
value = !value;
});
},
),
CupertinoSwitch(
key: switchKey,
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
],
),
);
},
),
),
);
expect(value, isFalse);
await tester.tap(find.byType(CupertinoButton));
expect(value, isTrue);
await tester.pump();
expect(log, hasLength(0));
},
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
);
testWidgets('Switch can drag (LTR)', (WidgetTester tester) async {
bool value = false;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: CupertinoSwitch(
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
);
},
),
),
);
expect(value, isFalse);
await tester.drag(find.byType(CupertinoSwitch), const Offset(-48.0, 0.0));
expect(value, isFalse);
await tester.drag(find.byType(CupertinoSwitch), const Offset(48.0, 0.0));
expect(value, isTrue);
await tester.pump();
await tester.drag(find.byType(CupertinoSwitch), const Offset(48.0, 0.0));
expect(value, isTrue);
await tester.pump();
await tester.drag(find.byType(CupertinoSwitch), const Offset(-48.0, 0.0));
expect(value, isFalse);
});
testWidgets('Switch can drag with dragStartBehavior', (WidgetTester tester) async {
bool value = false;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: CupertinoSwitch(
value: value,
dragStartBehavior: DragStartBehavior.down,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
);
},
),
),
);
expect(value, isFalse);
await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0));
expect(value, isFalse);
await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0));
expect(value, isTrue);
await tester.pump();
await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0));
expect(value, isTrue);
await tester.pump();
await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0));
expect(value, isFalse);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: CupertinoSwitch(
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
);
},
),
),
);
await tester.pumpAndSettle();
final Rect switchRect = tester.getRect(find.byType(CupertinoSwitch));
expect(value, isFalse);
TestGesture gesture = await tester.startGesture(switchRect.center);
// We have to execute the drag in two frames because the first update will
// just set the start position.
await gesture.moveBy(const Offset(20.0, 0.0));
await gesture.moveBy(const Offset(20.0, 0.0));
expect(value, isFalse);
await gesture.up();
expect(value, isTrue);
await tester.pump();
gesture = await tester.startGesture(switchRect.center);
await gesture.moveBy(const Offset(20.0, 0.0));
await gesture.moveBy(const Offset(20.0, 0.0));
expect(value, isTrue);
await gesture.up();
await tester.pump();
gesture = await tester.startGesture(switchRect.center);
await gesture.moveBy(const Offset(-20.0, 0.0));
await gesture.moveBy(const Offset(-20.0, 0.0));
expect(value, isTrue);
await gesture.up();
expect(value, isFalse);
await tester.pump();
});
testWidgets('Switch can drag (RTL)', (WidgetTester tester) async {
bool value = false;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.rtl,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: CupertinoSwitch(
value: value,
dragStartBehavior: DragStartBehavior.down,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
);
},
),
),
);
expect(value, isFalse);
await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0));
expect(value, isFalse);
await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0));
expect(value, isTrue);
await tester.pump();
await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0));
expect(value, isTrue);
await tester.pump();
await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0));
expect(value, isFalse);
});
testWidgets('can veto switch dragging result', (WidgetTester tester) async {
bool value = false;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: Center(
child: CupertinoSwitch(
dragStartBehavior: DragStartBehavior.down,
value: value,
onChanged: (bool newValue) {
setState(() {
value = value || newValue;
});
},
),
),
);
},
),
),
);
// Move a little to the right, not past the middle.
TestGesture gesture = await tester.startGesture(
tester.getRect(find.byType(CupertinoSwitch)).center,
);
await gesture.moveBy(const Offset(kTouchSlop + 0.1, 0.0));
await tester.pump();
await gesture.moveBy(const Offset(-kTouchSlop + 5.1, 0.0));
await tester.pump();
await gesture.up();
await tester.pump();
expect(value, isFalse);
final CurvedAnimation position =
(tester.state(find.byType(CupertinoSwitch)) as dynamic).position as CurvedAnimation;
expect(position.value, lessThan(0.5));
await tester.pump();
await tester.pumpAndSettle();
expect(value, isFalse);
expect(position.value, 0);
// Move past the middle.
gesture = await tester.startGesture(tester.getRect(find.byType(CupertinoSwitch)).center);
await gesture.moveBy(const Offset(kTouchSlop + 0.1, 0.0));
await tester.pump();
await gesture.up();
await tester.pump();
expect(value, isTrue);
expect(position.value, greaterThan(0.5));
await tester.pump();
await tester.pumpAndSettle();
expect(value, isTrue);
expect(position.value, 1.0);
// Now move back to the left, the revert animation should play.
gesture = await tester.startGesture(tester.getRect(find.byType(CupertinoSwitch)).center);
await gesture.moveBy(const Offset(-kTouchSlop - 0.1, 0.0));
await tester.pump();
await gesture.up();
await tester.pump();
expect(value, isTrue);
expect(position.value, lessThan(0.5));
await tester.pump();
await tester.pumpAndSettle();
expect(value, isTrue);
expect(position.value, 1.0);
});
testWidgets('Switch is translucent when disabled', (WidgetTester tester) async {
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: CupertinoSwitch(
value: false,
dragStartBehavior: DragStartBehavior.down,
onChanged: null,
),
),
),
);
expect(find.byType(Opacity), findsOneWidget);
expect(tester.widget<Opacity>(find.byType(Opacity).first).opacity, 0.5);
});
testWidgets('Switch is using track color when set', (WidgetTester tester) async {
const Color trackColor = Color(0xFF00FF00);
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: CupertinoSwitch(
value: false,
trackColor: trackColor,
dragStartBehavior: DragStartBehavior.down,
onChanged: null,
),
),
),
);
expect(find.byType(CupertinoSwitch), findsOneWidget);
expect(tester.widget<CupertinoSwitch>(find.byType(CupertinoSwitch)).trackColor, trackColor);
expect(find.byType(CupertinoSwitch), paints..rrect(color: trackColor));
});
testWidgets('Switch is using default thumb color', (WidgetTester tester) async {
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.ltr,
child: Center(child: CupertinoSwitch(value: false, onChanged: null)),
),
);
expect(find.byType(CupertinoSwitch), findsOneWidget);
expect(tester.widget<CupertinoSwitch>(find.byType(CupertinoSwitch)).thumbColor, null);
expect(
find.byType(CupertinoSwitch),
paints
..rrect()
..rrect()
..rrect()
..rrect()
..rrect(color: CupertinoColors.white),
);
});
testWidgets('Switch is using thumb color when set', (WidgetTester tester) async {
const Color thumbColor = Color(0xFF000000);
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: CupertinoSwitch(value: false, thumbColor: thumbColor, onChanged: null),
),
),
);
expect(find.byType(CupertinoSwitch), findsOneWidget);
expect(tester.widget<CupertinoSwitch>(find.byType(CupertinoSwitch)).thumbColor, thumbColor);
expect(
find.byType(CupertinoSwitch),
paints
..rrect()
..rrect()
..rrect()
..rrect()
..rrect(color: thumbColor),
);
});
testWidgets('Switch can set active/inactive thumb colors', (WidgetTester tester) async {
bool value = false;
const Color activeThumbColor = Color(0xff00000A);
const Color inactiveThumbColor = Color(0xff00000B);
await tester.pumpWidget(
CupertinoApp(
home: Directionality(
textDirection: TextDirection.rtl,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return CupertinoPageScaffold(
child: Center(
child: CupertinoSwitch(
dragStartBehavior: DragStartBehavior.down,
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
thumbColor: activeThumbColor,
inactiveThumbColor: inactiveThumbColor,
),
),
);
},
),
),
),
);
expect(
find.byType(CupertinoSwitch),
paints
..rrect()
..rrect()
..rrect()
..rrect()
..rrect(color: inactiveThumbColor),
);
await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0));
await tester.pump();
expect(
find.byType(CupertinoSwitch),
paints
..rrect()
..rrect()
..rrect()
..rrect()
..rrect(color: activeThumbColor),
);
});
testWidgets('Switch is opaque when enabled', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: CupertinoSwitch(
value: false,
dragStartBehavior: DragStartBehavior.down,
onChanged: (bool newValue) {},
),
),
),
);
expect(find.byType(Opacity), findsOneWidget);
expect(tester.widget<Opacity>(find.byType(Opacity).first).opacity, 1.0);
});
testWidgets('Switch turns translucent after becoming disabled', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: CupertinoSwitch(
value: false,
dragStartBehavior: DragStartBehavior.down,
onChanged: (bool newValue) {},
),
),
),
);
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: CupertinoSwitch(
value: false,
dragStartBehavior: DragStartBehavior.down,
onChanged: null,
),
),
),
);
expect(find.byType(Opacity), findsOneWidget);
expect(tester.widget<Opacity>(find.byType(Opacity).first).opacity, 0.5);
});
testWidgets('Switch turns opaque after becoming enabled', (WidgetTester tester) async {
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: CupertinoSwitch(
value: false,
dragStartBehavior: DragStartBehavior.down,
onChanged: null,
),
),
),
);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: CupertinoSwitch(
value: false,
dragStartBehavior: DragStartBehavior.down,
onChanged: (bool newValue) {},
),
),
),
);
expect(find.byType(Opacity), findsOneWidget);
expect(tester.widget<Opacity>(find.byType(Opacity).first).opacity, 1.0);
});
testWidgets('Switch renders correctly before, during, and after being tapped', (
WidgetTester tester,
) async {
final Key switchKey = UniqueKey();
bool value = false;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: RepaintBoundary(
child: CupertinoSwitch(
key: switchKey,
value: value,
dragStartBehavior: DragStartBehavior.down,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
),
);
await expectLater(find.byKey(switchKey), matchesGoldenFile('switch.tap.off.png'));
await tester.tap(find.byKey(switchKey));
expect(value, isTrue);
// Kick off animation, then advance to intermediate frame.
await tester.pump();
await tester.pump(const Duration(milliseconds: 60));
await expectLater(find.byKey(switchKey), matchesGoldenFile('switch.tap.turningOn.png'));
await tester.pumpAndSettle();
await expectLater(find.byKey(switchKey), matchesGoldenFile('switch.tap.on.png'));
});
PaintPattern onLabelPaintPattern({required int alpha, bool isRtl = false}) =>
paints..rect(
rect: Rect.fromLTWH(isRtl ? 43.5 : 14.5, 14.5, 1.0, 10.0),
color: const Color(0xffffffff).withAlpha(alpha),
style: PaintingStyle.fill,
);
PaintPattern offLabelPaintPattern({
required int alpha,
bool highContrast = false,
bool isRtl = false,
}) =>
paints..circle(
x: isRtl ? 16.0 : 43.0,
y: 19.5,
radius: 5.0,
color: (highContrast ? const Color(0xffffffff) : const Color(0xffb3b3b3)).withAlpha(alpha),
strokeWidth: 1.0,
style: PaintingStyle.stroke,
);
testWidgets('Switch renders switch labels correctly before, during, and after being tapped', (
WidgetTester tester,
) async {
final Key switchKey = UniqueKey();
bool value = false;
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(onOffSwitchLabels: true),
child: Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: RepaintBoundary(
child: CupertinoSwitch(
key: switchKey,
value: value,
dragStartBehavior: DragStartBehavior.down,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
),
),
);
final RenderObject switchRenderObject =
tester.element(find.byType(CupertinoSwitch)).renderObject!;
expect(switchRenderObject, offLabelPaintPattern(alpha: 255));
expect(switchRenderObject, onLabelPaintPattern(alpha: 0));
await tester.tap(find.byKey(switchKey));
expect(value, isTrue);
// Kick off animation, then advance to intermediate frame.
await tester.pump();
await tester.pump(const Duration(milliseconds: 60));
expect(switchRenderObject, onLabelPaintPattern(alpha: 131));
expect(switchRenderObject, offLabelPaintPattern(alpha: 124));
await tester.pumpAndSettle();
expect(switchRenderObject, onLabelPaintPattern(alpha: 255));
expect(switchRenderObject, offLabelPaintPattern(alpha: 0));
});
testWidgets(
'Switch renders switch labels correctly before, during, and after being tapped in high contrast',
(WidgetTester tester) async {
final Key switchKey = UniqueKey();
bool value = false;
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(onOffSwitchLabels: true, highContrast: true),
child: Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: RepaintBoundary(
child: CupertinoSwitch(
key: switchKey,
value: value,
dragStartBehavior: DragStartBehavior.down,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
),
),
);
final RenderObject switchRenderObject =
tester.element(find.byType(CupertinoSwitch)).renderObject!;
expect(switchRenderObject, offLabelPaintPattern(highContrast: true, alpha: 255));
expect(switchRenderObject, onLabelPaintPattern(alpha: 0));
await tester.tap(find.byKey(switchKey));
expect(value, isTrue);
// Kick off animation, then advance to intermediate frame.
await tester.pump();
await tester.pump(const Duration(milliseconds: 60));
expect(switchRenderObject, onLabelPaintPattern(alpha: 131));
expect(switchRenderObject, offLabelPaintPattern(highContrast: true, alpha: 124));
await tester.pumpAndSettle();
expect(switchRenderObject, onLabelPaintPattern(alpha: 255));
expect(switchRenderObject, offLabelPaintPattern(highContrast: true, alpha: 0));
},
);
testWidgets(
'Switch renders switch labels correctly before, during, and after being tapped with direction rtl',
(WidgetTester tester) async {
final Key switchKey = UniqueKey();
bool value = false;
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(onOffSwitchLabels: true),
child: Directionality(
textDirection: TextDirection.rtl,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: RepaintBoundary(
child: CupertinoSwitch(
key: switchKey,
value: value,
dragStartBehavior: DragStartBehavior.down,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
),
),
);
final RenderObject switchRenderObject =
tester.element(find.byType(CupertinoSwitch)).renderObject!;
expect(switchRenderObject, offLabelPaintPattern(isRtl: true, alpha: 255));
expect(switchRenderObject, onLabelPaintPattern(isRtl: true, alpha: 0));
await tester.tap(find.byKey(switchKey));
expect(value, isTrue);
// Kick off animation, then advance to intermediate frame.
await tester.pump();
await tester.pump(const Duration(milliseconds: 60));
expect(switchRenderObject, onLabelPaintPattern(isRtl: true, alpha: 131));
expect(switchRenderObject, offLabelPaintPattern(isRtl: true, alpha: 124));
await tester.pumpAndSettle();
expect(switchRenderObject, onLabelPaintPattern(isRtl: true, alpha: 255));
expect(switchRenderObject, offLabelPaintPattern(isRtl: true, alpha: 0));
},
);
testWidgets('Switch renders correctly in dark mode', (WidgetTester tester) async {
final Key switchKey = UniqueKey();
bool value = false;
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(platformBrightness: Brightness.dark),
child: Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: RepaintBoundary(
child: CupertinoSwitch(
key: switchKey,
value: value,
dragStartBehavior: DragStartBehavior.down,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
),
),
);
await expectLater(find.byKey(switchKey), matchesGoldenFile('switch.tap.off.dark.png'));
await tester.tap(find.byKey(switchKey));
expect(value, isTrue);
await tester.pumpAndSettle();
await expectLater(find.byKey(switchKey), matchesGoldenFile('switch.tap.on.dark.png'));
});
testWidgets('Switch can apply the ambient theme and be opted out', (WidgetTester tester) async {
final Key switchKey = UniqueKey();
bool value = false;
await tester.pumpWidget(
CupertinoTheme(
data: const CupertinoThemeData(primaryColor: Colors.amber, applyThemeToAll: true),
child: Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: RepaintBoundary(
child: Column(
children: <Widget>[
CupertinoSwitch(
key: switchKey,
value: value,
dragStartBehavior: DragStartBehavior.down,
applyTheme: true,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
CupertinoSwitch(
value: value,
dragStartBehavior: DragStartBehavior.down,
applyTheme: false,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
],
),
),
);
},
),
),
),
);
await expectLater(find.byType(Column), matchesGoldenFile('switch.tap.off.themed.png'));
await tester.tap(find.byKey(switchKey));
expect(value, isTrue);
await tester.pumpAndSettle();
await expectLater(find.byType(Column), matchesGoldenFile('switch.tap.on.themed.png'));
});
testWidgets('Hovering over switch updates cursor to clickable on Web', (
WidgetTester tester,
) async {
const bool value = false;
// Disabled CupertinoSwitch does not update cursor on Web.
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return const Center(
child: CupertinoSwitch(
value: value,
dragStartBehavior: DragStartBehavior.down,
onChanged: null,
),
);
},
),
),
);
final TestGesture gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
pointer: 1,
);
final Offset cupertinoSwitch = tester.getCenter(find.byType(CupertinoSwitch));
await gesture.addPointer(location: cupertinoSwitch);
await tester.pumpAndSettle();
expect(
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
SystemMouseCursors.basic,
);
// Enabled CupertinoSwitch updates cursor when hovering on Web.
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: CupertinoSwitch(
value: value,
dragStartBehavior: DragStartBehavior.down,
onChanged: (bool newValue) {},
),
);
},
),
),
);
await gesture.moveTo(const Offset(10, 10));
await tester.pumpAndSettle();
expect(
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
SystemMouseCursors.basic,
);
await gesture.moveTo(cupertinoSwitch);
await tester.pumpAndSettle();
expect(
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
kIsWeb ? SystemMouseCursors.click : SystemMouseCursors.basic,
);
});
testWidgets('Switch configures mouse cursor', (WidgetTester tester) async {
const bool value = false;
const Offset switchSize = Offset(51.0, 31.0);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: CupertinoSwitch(
value: value,
dragStartBehavior: DragStartBehavior.down,
mouseCursor: WidgetStateProperty.all(SystemMouseCursors.forbidden),
onChanged: (bool newValue) {},
),
);
},
),
),
);
final TestGesture gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
pointer: 1,
);
// The pointer is not pointing at the switch.
await gesture.addPointer(location: tester.getCenter(find.byType(CupertinoSwitch)) + switchSize);
await tester.pump();
expect(
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
SystemMouseCursors.basic,
);
// The pointer now points at the switch.
await gesture.moveTo(tester.getCenter(find.byType(CupertinoSwitch)));
await tester.pump();
expect(
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
SystemMouseCursors.forbidden,
);
});
testWidgets('CupertinoSwitch is focusable and has correct focus color', (
WidgetTester tester,
) async {
final FocusNode focusNode = FocusNode(debugLabel: 'CupertinoSwitch');
addTearDown(focusNode.dispose);
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
bool value = true;
const Color focusColor = Color(0xffff0000);
Widget buildApp({bool enabled = true}) {
return Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: CupertinoSwitch(
value: value,
onChanged:
enabled
? (bool newValue) {
setState(() {
value = newValue;
});
}
: null,
focusColor: focusColor,
focusNode: focusNode,
autofocus: true,
),
);
},
),
);
}
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
find.byType(CupertinoSwitch),
paints
..rrect(color: const Color(0xff34c759))
..rrect(color: focusColor)
..clipRRect()
..rrect(color: const Color(0x26000000))
..rrect(color: const Color(0x0f000000))
..rrect(color: const Color(0x0a000000))
..rrect(color: const Color(0xffffffff)),
);
// Check the false value.
value = false;
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
find.byType(CupertinoSwitch),
paints
..rrect(color: const Color(0x28787880))
..rrect(color: focusColor)
..clipRRect()
..rrect(color: const Color(0x26000000))
..rrect(color: const Color(0x0f000000))
..rrect(color: const Color(0x0a000000))
..rrect(color: const Color(0xffffffff)),
);
// Check what happens when disabled.
value = false;
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isFalse);
expect(
find.byType(CupertinoSwitch),
paints
..rrect(color: const Color(0x28787880))
..clipRRect()
..rrect(color: const Color(0x26000000))
..rrect(color: const Color(0x0f000000))
..rrect(color: const Color(0x0a000000))
..rrect(color: const Color(0xffffffff)),
);
});
testWidgets('CupertinoSwitch.onFocusChange callback', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'CupertinoSwitch');
addTearDown(focusNode.dispose);
bool focused = false;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: CupertinoSwitch(
value: true,
focusNode: focusNode,
onFocusChange: (bool value) {
focused = value;
},
onChanged: (bool newValue) {},
),
),
),
);
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('Switch has semantic events', (WidgetTester tester) async {
dynamic semanticEvent;
bool value = false;
tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(
SystemChannels.accessibility,
(dynamic message) async {
semanticEvent = message;
},
);
final SemanticsTester semanticsTester = SemanticsTester(tester);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: Center(
child: CupertinoSwitch(
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
),
);
await tester.tap(find.byType(CupertinoSwitch));
final RenderObject object = tester.firstRenderObject(find.byType(CupertinoSwitch));
expect(value, true);
expect(semanticEvent, <String, dynamic>{
'type': 'tap',
'nodeId': object.debugSemantics!.id,
'data': <String, dynamic>{},
});
expect(object.debugSemantics!.getSemanticsData().hasAction(SemanticsAction.tap), true);
semanticsTester.dispose();
tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(
SystemChannels.accessibility,
null,
);
});
testWidgets('Switch sends semantic events from parent if fully merged', (
WidgetTester tester,
) async {
dynamic semanticEvent;
bool value = false;
tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(
SystemChannels.accessibility,
(dynamic message) async {
semanticEvent = message;
},
);
final SemanticsTester semanticsTester = SemanticsTester(tester);
await tester.pumpWidget(
CupertinoApp(
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
void onChanged(bool newValue) {
setState(() {
value = newValue;
});
}
return Material(
child: MergeSemantics(
child: ListTile(
title: const Text('test'),
onTap: () {
onChanged(!value);
},
trailing: CupertinoSwitch(value: value, onChanged: onChanged),
),
),
);
},
),
),
);
await tester.tap(find.byType(MergeSemantics));
final RenderObject object = tester.firstRenderObject(find.byType(MergeSemantics));
expect(value, true);
expect(semanticEvent, <String, dynamic>{
'type': 'tap',
'nodeId': object.debugSemantics!.id,
'data': <String, dynamic>{},
});
expect(object.debugSemantics!.getSemanticsData().hasAction(SemanticsAction.tap), true);
semanticsTester.dispose();
tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(
SystemChannels.accessibility,
null,
);
});
testWidgets('Track outline color resolves in active/enabled states', (WidgetTester tester) async {
const Color activeEnabledTrackOutlineColor = Color(0xFF000001);
const Color activeDisabledTrackOutlineColor = Color(0xFF000002);
const Color inactiveEnabledTrackOutlineColor = Color(0xFF000003);
const Color inactiveDisabledTrackOutlineColor = Color(0xFF000004);
Color getTrackOutlineColor(Set<WidgetState> states) {
if (states.contains(WidgetState.disabled)) {
if (states.contains(WidgetState.selected)) {
return activeDisabledTrackOutlineColor;
}
return inactiveDisabledTrackOutlineColor;
}
if (states.contains(WidgetState.selected)) {
return activeEnabledTrackOutlineColor;
}
return inactiveEnabledTrackOutlineColor;
}
final WidgetStateProperty<Color> trackOutlineColor = WidgetStateColor.resolveWith(
getTrackOutlineColor,
);
Widget buildSwitch({required bool enabled, required bool active}) {
return Directionality(
textDirection: TextDirection.rtl,
child: CupertinoPageScaffold(
child: Center(
child: CupertinoSwitch(
trackOutlineColor: trackOutlineColor,
value: active,
onChanged: enabled ? (_) {} : null,
),
),
),
);
}
await tester.pumpWidget(buildSwitch(enabled: false, active: false));
expect(
find.byType(CupertinoSwitch),
paints
..rrect(style: PaintingStyle.fill)
..rrect(color: inactiveDisabledTrackOutlineColor, style: PaintingStyle.stroke),
reason: 'Inactive disabled switch track outline should use this value',
);
await tester.pumpWidget(buildSwitch(enabled: false, active: true));
await tester.pumpAndSettle();
expect(
find.byType(CupertinoSwitch),
paints
..rrect(style: PaintingStyle.fill)
..rrect(color: activeDisabledTrackOutlineColor, style: PaintingStyle.stroke),
reason: 'Active disabled switch track outline should match these colors',
);
await tester.pumpWidget(buildSwitch(enabled: true, active: false));
await tester.pumpAndSettle();
expect(
find.byType(CupertinoSwitch),
paints
..rrect(style: PaintingStyle.fill)
..rrect(color: inactiveEnabledTrackOutlineColor),
reason: 'Inactive enabled switch track outline should match these colors',
);
await tester.pumpWidget(buildSwitch(enabled: true, active: true));
await tester.pumpAndSettle();
expect(
find.byType(CupertinoSwitch),
paints
..rrect(style: PaintingStyle.fill)
..rrect(color: activeEnabledTrackOutlineColor),
reason: 'Active enabled switch track outline should match these colors',
);
});
testWidgets('Switch track outline color resolves in hovered/focused states', (
WidgetTester tester,
) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Switch');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color hoveredTrackOutlineColor = Color(0xFF000001);
const Color focusedTrackOutlineColor = Color(0xFF000002);
Color getTrackOutlineColor(Set<WidgetState> states) {
if (states.contains(WidgetState.hovered)) {
return hoveredTrackOutlineColor;
}
if (states.contains(WidgetState.focused)) {
return focusedTrackOutlineColor;
}
return Colors.transparent;
}
final WidgetStateProperty<Color> trackOutlineColor = WidgetStateColor.resolveWith(
getTrackOutlineColor,
);
Widget buildSwitch() {
return Directionality(
textDirection: TextDirection.rtl,
child: Material(
child: Center(
child: CupertinoSwitch(
focusNode: focusNode,
autofocus: true,
value: true,
trackOutlineColor: trackOutlineColor,
onChanged: (_) {},
),
),
),
);
}
await tester.pumpWidget(buildSwitch());
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
find.byType(CupertinoSwitch),
paints
..rrect(style: PaintingStyle.fill)
..rrect(color: focusedTrackOutlineColor, style: PaintingStyle.stroke),
reason: 'Active enabled switch track outline should match this color',
);
// Start hovering.
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(CupertinoSwitch)));
await tester.pumpAndSettle();
expect(
find.byType(CupertinoSwitch),
paints
..rrect(style: PaintingStyle.fill)
..rrect(color: hoveredTrackOutlineColor, style: PaintingStyle.stroke),
reason: 'Active enabled switch track outline should match this color',
);
focusNode.dispose();
});
testWidgets('Track outline width resolves in active/enabled states', (WidgetTester tester) async {
const double activeEnabledTrackOutlineWidth = 1.0;
const double activeDisabledTrackOutlineWidth = 2.0;
const double inactiveEnabledTrackOutlineWidth = 3.0;
const double inactiveDisabledTrackOutlineWidth = 4.0;
double getTrackOutlineWidth(Set<WidgetState> states) {
if (states.contains(WidgetState.disabled)) {
if (states.contains(WidgetState.selected)) {
return activeDisabledTrackOutlineWidth;
}
return inactiveDisabledTrackOutlineWidth;
}
if (states.contains(WidgetState.selected)) {
return activeEnabledTrackOutlineWidth;
}
return inactiveEnabledTrackOutlineWidth;
}
final WidgetStateProperty<double> trackOutlineWidth = WidgetStateProperty.resolveWith(
getTrackOutlineWidth,
);
const WidgetStateProperty<Color> trackOutlineColor = WidgetStatePropertyAll<Color>(
Color(0xFFFFFFFF),
);
Widget buildSwitch({required bool enabled, required bool active}) {
return CupertinoApp(
home: CupertinoPageScaffold(
child: Center(
child: CupertinoSwitch(
trackOutlineWidth: trackOutlineWidth,
trackOutlineColor: trackOutlineColor,
value: active,
onChanged: enabled ? (_) {} : null,
),
),
),
);
}
await tester.pumpWidget(buildSwitch(enabled: false, active: false));
expect(
find.byType(CupertinoSwitch),
paints
..rrect(style: PaintingStyle.fill)
..rrect(strokeWidth: inactiveDisabledTrackOutlineWidth, style: PaintingStyle.stroke),
reason: 'Inactive disabled switch track outline width should be 4.0',
);
await tester.pumpWidget(buildSwitch(enabled: false, active: true));
await tester.pumpAndSettle();
expect(
find.byType(CupertinoSwitch),
paints
..rrect(style: PaintingStyle.fill)
..rrect(strokeWidth: activeDisabledTrackOutlineWidth, style: PaintingStyle.stroke),
reason: 'Active disabled switch track outline width should be 2.0',
);
await tester.pumpWidget(buildSwitch(enabled: true, active: false));
await tester.pumpAndSettle();
expect(
find.byType(CupertinoSwitch),
paints
..rrect(style: PaintingStyle.fill)
..rrect(strokeWidth: inactiveEnabledTrackOutlineWidth, style: PaintingStyle.stroke),
reason: 'Inactive enabled switch track outline width should be 3.0',
);
await tester.pumpWidget(buildSwitch(enabled: true, active: true));
await tester.pumpAndSettle();
expect(
find.byType(CupertinoSwitch),
paints
..rrect(style: PaintingStyle.fill)
..rrect(strokeWidth: activeEnabledTrackOutlineWidth, style: PaintingStyle.stroke),
reason: 'Active enabled switch track outline width should be 1.0',
);
});
testWidgets('Switch track outline width resolves in hovered/focused states', (
WidgetTester tester,
) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Switch');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const double hoveredTrackOutlineWidth = 4.0;
const double focusedTrackOutlineWidth = 6.0;
double getTrackOutlineWidth(Set<WidgetState> states) {
if (states.contains(WidgetState.hovered)) {
return hoveredTrackOutlineWidth;
}
if (states.contains(WidgetState.focused)) {
return focusedTrackOutlineWidth;
}
return 8.0;
}
final WidgetStateProperty<double> trackOutlineWidth = WidgetStateProperty.resolveWith(
getTrackOutlineWidth,
);
const WidgetStateProperty<Color> trackOutlineColor = WidgetStatePropertyAll<Color>(
Color(0xFFFFFFFF),
);
Widget buildSwitch() {
return MaterialApp(
home: Material(
child: Center(
child: CupertinoSwitch(
focusNode: focusNode,
autofocus: true,
value: true,
trackOutlineWidth: trackOutlineWidth,
trackOutlineColor: trackOutlineColor,
onChanged: (_) {},
),
),
),
);
}
await tester.pumpWidget(buildSwitch());
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
find.byType(CupertinoSwitch),
paints
..rrect(style: PaintingStyle.fill)
..rrect(strokeWidth: focusedTrackOutlineWidth, style: PaintingStyle.stroke)
..rrect(strokeWidth: 3.5, style: PaintingStyle.stroke),
reason: 'Active enabled switch track outline width should be 6.0',
);
// Start hovering.
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(CupertinoSwitch)));
await tester.pumpAndSettle();
expect(
find.byType(CupertinoSwitch),
paints
..rrect(style: PaintingStyle.fill)
..rrect(strokeWidth: hoveredTrackOutlineWidth, style: PaintingStyle.stroke),
reason: 'Active enabled switch track outline width should be 4.0',
);
focusNode.dispose();
});
testWidgets('Switch can set icon', (WidgetTester tester) async {
WidgetStateProperty<Icon?> thumbIcon(Icon? activeIcon, Icon? inactiveIcon) {
return WidgetStateProperty.resolveWith<Icon?>((Set<WidgetState> states) {
if (states.contains(WidgetState.selected)) {
return activeIcon;
}
return inactiveIcon;
});
}
Widget buildSwitch({
required bool enabled,
required bool active,
Icon? activeIcon,
Icon? inactiveIcon,
}) {
return Directionality(
textDirection: TextDirection.ltr,
child: CupertinoPageScaffold(
child: Center(
child: CupertinoSwitch(
thumbIcon: thumbIcon(activeIcon, inactiveIcon),
value: active,
onChanged: enabled ? (_) {} : null,
),
),
),
);
}
// The active icon shows when the switch is on.
await tester.pumpWidget(
buildSwitch(enabled: true, active: true, activeIcon: const Icon(Icons.close)),
);
await tester.pumpAndSettle();
expect(
find.byType(CupertinoSwitch),
paints
..rrect()
..rrect()
..paragraph(offset: const Offset(31.5, 11.5)),
);
// The inactive icon shows when the switch is off.
await tester.pumpWidget(
buildSwitch(enabled: true, active: false, inactiveIcon: const Icon(Icons.close)),
);
await tester.pumpAndSettle();
expect(
find.byType(CupertinoSwitch),
paints
..rrect()
..rrect()
..rrect()
..paragraph(offset: const Offset(11.5, 11.5)),
);
// The active icon doesn't show when the switch is off.
await tester.pumpWidget(
buildSwitch(enabled: true, active: false, activeIcon: const Icon(Icons.check)),
);
await tester.pumpAndSettle();
expect(
find.byType(CupertinoSwitch),
paints
..rrect()
..rrect()
..rrect(),
);
// The inactive icon doesn't show when the switch is on.
await tester.pumpWidget(
buildSwitch(enabled: true, active: true, inactiveIcon: const Icon(Icons.check)),
);
await tester.pumpAndSettle();
expect(
find.byType(CupertinoSwitch),
paints
..rrect()
..rrect()
..restore(),
);
// No icons are shown.
await tester.pumpWidget(buildSwitch(enabled: true, active: false));
expect(
find.byType(CupertinoSwitch),
paints
..rrect()
..rrect()
..rrect()
..restore(),
);
});
group('with image', () {
late ui.Image image;
setUp(() async {
image = await createTestImage(width: 100, height: 100);
});
testWidgets('Thumb images show up when set', (WidgetTester tester) async {
imageCache.clear();
final _TestImageProvider provider1 = _TestImageProvider();
final _TestImageProvider provider2 = _TestImageProvider();
expect(provider1.loadCallCount, 0);
expect(provider2.loadCallCount, 0);
bool value1 = true;
await tester.pumpWidget(
CupertinoApp(
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return CupertinoPageScaffold(
child: CupertinoSwitch(
activeThumbImage: provider1,
inactiveThumbImage: provider2,
value: value1,
onChanged: (bool val) {
setState(() {
value1 = val;
});
},
),
);
},
),
),
);
expect(provider1.loadCallCount, 1);
expect(provider2.loadCallCount, 0);
expect(imageCache.liveImageCount, 1);
await tester.tap(find.byType(CupertinoSwitch));
await tester.pumpAndSettle();
expect(provider1.loadCallCount, 1);
expect(provider2.loadCallCount, 1);
expect(imageCache.liveImageCount, 2);
});
testWidgets('Does not crash when imageProvider completes after switch is disposed', (
WidgetTester tester,
) async {
final DelayedImageProvider imageProvider = DelayedImageProvider(image);
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
child: Center(
child: CupertinoSwitch(
value: true,
onChanged: null,
inactiveThumbImage: imageProvider,
),
),
),
),
);
expect(find.byType(CupertinoSwitch), findsOneWidget);
// Dispose the switch by taking down the tree.
await tester.pumpWidget(Container());
expect(find.byType(CupertinoSwitch), findsNothing);
imageProvider.complete();
expect(tester.takeException(), isNull);
});
testWidgets('Does not crash when previous imageProvider completes after switch is disposed', (
WidgetTester tester,
) async {
final DelayedImageProvider imageProvider1 = DelayedImageProvider(image);
final DelayedImageProvider imageProvider2 = DelayedImageProvider(image);
Future<void> buildSwitch(ImageProvider imageProvider) {
return tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
child: Center(
child: CupertinoSwitch(
value: true,
onChanged: null,
inactiveThumbImage: imageProvider,
),
),
),
),
);
}
await buildSwitch(imageProvider1);
expect(find.byType(CupertinoSwitch), findsOneWidget);
// Replace the ImageProvider.
await buildSwitch(imageProvider2);
expect(find.byType(CupertinoSwitch), findsOneWidget);
// Dispose the switch by taking down the tree.
await tester.pumpWidget(Container());
expect(find.byType(CupertinoSwitch), findsNothing);
// Completing the replaced ImageProvider shouldn't crash.
imageProvider1.complete();
expect(tester.takeException(), isNull);
imageProvider2.complete();
expect(tester.takeException(), isNull);
});
testWidgets('Switch uses inactive track color when set', (WidgetTester tester) async {
const Color inactiveTrackColor = Color(0xFF00FF00);
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: CupertinoSwitch(
value: false,
inactiveTrackColor: inactiveTrackColor,
dragStartBehavior: DragStartBehavior.down,
onChanged: null,
),
),
),
);
expect(find.byType(CupertinoSwitch), findsOneWidget);
expect(
tester.widget<CupertinoSwitch>(find.byType(CupertinoSwitch)).inactiveTrackColor,
inactiveTrackColor,
);
expect(find.byType(CupertinoSwitch), paints..rrect(color: inactiveTrackColor));
});
testWidgets('Switch uses active track color when set', (WidgetTester tester) async {
const Color activeTrackColor = Color(0xFF00FF00);
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: CupertinoSwitch(
value: true,
activeTrackColor: activeTrackColor,
dragStartBehavior: DragStartBehavior.down,
onChanged: null,
),
),
),
);
expect(find.byType(CupertinoSwitch), findsOneWidget);
expect(
tester.widget<CupertinoSwitch>(find.byType(CupertinoSwitch)).activeTrackColor,
activeTrackColor,
);
expect(find.byType(CupertinoSwitch), paints..rrect(color: activeTrackColor));
});
});
}
class _TestImageProvider extends ImageProvider<Object> {
_TestImageProvider({ImageStreamCompleter? streamCompleter}) {
_streamCompleter = streamCompleter ?? OneFrameImageStreamCompleter(_completer.future);
}
final Completer<ImageInfo> _completer = Completer<ImageInfo>();
late ImageStreamCompleter _streamCompleter;
bool get loadCalled => _loadCallCount > 0;
int get loadCallCount => _loadCallCount;
int _loadCallCount = 0;
@override
Future<Object> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<_TestImageProvider>(this);
}
@override
void resolveStreamForKey(
ImageConfiguration configuration,
ImageStream stream,
Object key,
ImageErrorListener handleError,
) {
super.resolveStreamForKey(configuration, stream, key, handleError);
}
@override
ImageStreamCompleter loadImage(Object key, ImageDecoderCallback decode) {
_loadCallCount += 1;
return _streamCompleter;
}
void complete(ui.Image image) {
_completer.complete(ImageInfo(image: image));
}
void fail(Object exception, StackTrace? stackTrace) {
_completer.completeError(exception, stackTrace);
}
@override
String toString() => '${describeIdentity(this)}()';
}
class DelayedImageProvider extends ImageProvider<DelayedImageProvider> {
DelayedImageProvider(this.image);
final ui.Image image;
final Completer<ImageInfo> _completer = Completer<ImageInfo>();
@override
Future<DelayedImageProvider> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<DelayedImageProvider>(this);
}
@override
ImageStreamCompleter loadImage(DelayedImageProvider key, ImageDecoderCallback decode) {
return OneFrameImageStreamCompleter(_completer.future);
}
Future<void> complete() async {
_completer.complete(ImageInfo(image: image));
}
@override
String toString() => '${describeIdentity(this)}()';
}