mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Bumps the Dart version to 3.8 across the repo (excluding engine/src/flutter/third_party) and applies formatting updates from Dart 3.8. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
2495 lines
79 KiB
Dart
2495 lines
79 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 'dart:ui';
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter/src/gestures/constants.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import '../widgets/semantics_tester.dart';
|
|
|
|
void main() {
|
|
final ThemeData theme = ThemeData();
|
|
setUp(() {
|
|
debugResetSemanticsIdCounter();
|
|
});
|
|
|
|
testWidgets('Checkbox size is configurable by ThemeData.materialTapTargetSize', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme.copyWith(materialTapTargetSize: MaterialTapTargetSize.padded),
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Material(
|
|
child: Center(child: Checkbox(value: true, onChanged: (bool? newValue) {})),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.getSize(find.byType(Checkbox)), const Size(48.0, 48.0));
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme.copyWith(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Material(
|
|
child: Center(child: Checkbox(value: true, onChanged: (bool? newValue) {})),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.getSize(find.byType(Checkbox)), const Size(40.0, 40.0));
|
|
});
|
|
|
|
testWidgets('Checkbox semantics', (WidgetTester tester) async {
|
|
final SemanticsHandle handle = tester.ensureSemantics();
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme,
|
|
child: Material(child: Checkbox(value: false, onChanged: (bool? b) {})),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.getSemantics(find.byType(Focus).last),
|
|
matchesSemantics(
|
|
hasCheckedState: true,
|
|
hasEnabledState: true,
|
|
isEnabled: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
isFocusable: true,
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme,
|
|
child: Material(child: Checkbox(value: true, onChanged: (bool? b) {})),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.getSemantics(find.byType(Focus).last),
|
|
matchesSemantics(
|
|
hasCheckedState: true,
|
|
hasEnabledState: true,
|
|
isChecked: true,
|
|
isEnabled: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
isFocusable: true,
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme,
|
|
child: const Material(child: Checkbox(value: false, onChanged: null)),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.getSemantics(find.byType(Checkbox)),
|
|
matchesSemantics(
|
|
hasCheckedState: true,
|
|
hasEnabledState: true,
|
|
// isFocusable is delayed by 1 frame.
|
|
isFocusable: true,
|
|
hasFocusAction: true,
|
|
),
|
|
);
|
|
|
|
await tester.pump();
|
|
// isFocusable should be false now after the 1 frame delay.
|
|
expect(
|
|
tester.getSemantics(find.byType(Checkbox)),
|
|
matchesSemantics(hasCheckedState: true, hasEnabledState: true),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme,
|
|
child: const Material(child: Checkbox(value: true, onChanged: null)),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.getSemantics(find.byType(Checkbox)),
|
|
matchesSemantics(hasCheckedState: true, hasEnabledState: true, isChecked: true),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme,
|
|
child: const Material(child: Checkbox(value: null, tristate: true, onChanged: null)),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.getSemantics(find.byType(Checkbox)),
|
|
matchesSemantics(hasCheckedState: true, hasEnabledState: true, isCheckStateMixed: true),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme,
|
|
child: const Material(child: Checkbox(value: true, tristate: true, onChanged: null)),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.getSemantics(find.byType(Checkbox)),
|
|
matchesSemantics(hasCheckedState: true, hasEnabledState: true, isChecked: true),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme,
|
|
child: const Material(child: Checkbox(value: false, tristate: true, onChanged: null)),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.getSemantics(find.byType(Checkbox)),
|
|
matchesSemantics(hasCheckedState: true, hasEnabledState: true),
|
|
);
|
|
|
|
// Check if semanticLabel is there.
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Theme(
|
|
data: theme,
|
|
child: Material(
|
|
child: Checkbox(semanticLabel: 'checkbox', value: true, onChanged: (bool? b) {}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.getSemantics(find.byType(Focus).last),
|
|
matchesSemantics(
|
|
label: 'checkbox',
|
|
textDirection: TextDirection.ltr,
|
|
hasCheckedState: true,
|
|
hasEnabledState: true,
|
|
isChecked: true,
|
|
isEnabled: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
isFocusable: true,
|
|
),
|
|
);
|
|
handle.dispose();
|
|
});
|
|
|
|
testWidgets('Can wrap Checkbox with Semantics', (WidgetTester tester) async {
|
|
final SemanticsHandle handle = tester.ensureSemantics();
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme,
|
|
child: Material(
|
|
child: Semantics(
|
|
label: 'foo',
|
|
textDirection: TextDirection.ltr,
|
|
child: Checkbox(value: false, onChanged: (bool? b) {}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.getSemantics(find.byType(Focus).last),
|
|
matchesSemantics(
|
|
label: 'foo',
|
|
textDirection: TextDirection.ltr,
|
|
hasCheckedState: true,
|
|
hasEnabledState: true,
|
|
isEnabled: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
isFocusable: true,
|
|
),
|
|
);
|
|
handle.dispose();
|
|
});
|
|
|
|
testWidgets('Checkbox tristate: true', (WidgetTester tester) async {
|
|
bool? checkBoxValue;
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme,
|
|
child: Material(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(
|
|
tristate: true,
|
|
value: checkBoxValue,
|
|
onChanged: (bool? value) {
|
|
setState(() {
|
|
checkBoxValue = value;
|
|
});
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.widget<Checkbox>(find.byType(Checkbox)).value, null);
|
|
|
|
await tester.tap(find.byType(Checkbox));
|
|
await tester.pumpAndSettle();
|
|
expect(checkBoxValue, false);
|
|
|
|
await tester.tap(find.byType(Checkbox));
|
|
await tester.pumpAndSettle();
|
|
expect(checkBoxValue, true);
|
|
|
|
await tester.tap(find.byType(Checkbox));
|
|
await tester.pumpAndSettle();
|
|
expect(checkBoxValue, null);
|
|
|
|
checkBoxValue = true;
|
|
await tester.pumpAndSettle();
|
|
expect(checkBoxValue, true);
|
|
|
|
checkBoxValue = null;
|
|
await tester.pumpAndSettle();
|
|
expect(checkBoxValue, null);
|
|
});
|
|
|
|
testWidgets('has semantics for tristate', (WidgetTester tester) async {
|
|
final SemanticsTester semantics = SemanticsTester(tester);
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme,
|
|
child: Material(
|
|
child: Checkbox(tristate: true, value: null, onChanged: (bool? newValue) {}),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
semantics.nodesWith(
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.hasCheckedState,
|
|
SemanticsFlag.hasEnabledState,
|
|
SemanticsFlag.isEnabled,
|
|
SemanticsFlag.isFocusable,
|
|
SemanticsFlag.isCheckStateMixed,
|
|
],
|
|
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
|
|
),
|
|
hasLength(1),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme,
|
|
child: Material(
|
|
child: Checkbox(tristate: true, value: true, onChanged: (bool? newValue) {}),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
semantics.nodesWith(
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.hasCheckedState,
|
|
SemanticsFlag.hasEnabledState,
|
|
SemanticsFlag.isEnabled,
|
|
SemanticsFlag.isChecked,
|
|
SemanticsFlag.isFocusable,
|
|
],
|
|
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
|
|
),
|
|
hasLength(1),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme,
|
|
child: Material(
|
|
child: Checkbox(tristate: true, value: false, onChanged: (bool? newValue) {}),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
semantics.nodesWith(
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.hasCheckedState,
|
|
SemanticsFlag.hasEnabledState,
|
|
SemanticsFlag.isEnabled,
|
|
SemanticsFlag.isFocusable,
|
|
],
|
|
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
|
|
),
|
|
hasLength(1),
|
|
);
|
|
|
|
semantics.dispose();
|
|
});
|
|
|
|
testWidgets('has semantic events', (WidgetTester tester) async {
|
|
dynamic semanticEvent;
|
|
bool? checkboxValue = false;
|
|
tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(
|
|
SystemChannels.accessibility,
|
|
(dynamic message) async {
|
|
semanticEvent = message;
|
|
},
|
|
);
|
|
final SemanticsTester semanticsTester = SemanticsTester(tester);
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme,
|
|
child: Material(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(
|
|
value: checkboxValue,
|
|
onChanged: (bool? value) {
|
|
setState(() {
|
|
checkboxValue = value;
|
|
});
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.byType(Checkbox));
|
|
final RenderObject object = tester.firstRenderObject(find.byType(Checkbox));
|
|
|
|
expect(checkboxValue, true);
|
|
expect(semanticEvent, <String, dynamic>{
|
|
'type': 'tap',
|
|
'nodeId': object.debugSemantics!.id,
|
|
'data': <String, dynamic>{},
|
|
});
|
|
expect(object.debugSemantics!.getSemanticsData().hasAction(SemanticsAction.tap), true);
|
|
|
|
tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(
|
|
SystemChannels.accessibility,
|
|
null,
|
|
);
|
|
semanticsTester.dispose();
|
|
});
|
|
|
|
testWidgets('Material2 - Checkbox tristate rendering, programmatic transitions', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final ThemeData theme = ThemeData(useMaterial3: false);
|
|
Widget buildFrame(bool? checkboxValue) {
|
|
return Theme(
|
|
data: theme,
|
|
child: Material(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(tristate: true, value: checkboxValue, onChanged: (bool? value) {});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
RenderBox getCheckboxRenderer() {
|
|
return tester.renderObject<RenderBox>(find.byType(Checkbox));
|
|
}
|
|
|
|
await tester.pumpWidget(buildFrame(false));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
getCheckboxRenderer(),
|
|
paints..path(color: Colors.transparent),
|
|
); // paint transparent border
|
|
expect(getCheckboxRenderer(), isNot(paints..line())); // null is rendered as a line (a "dash")
|
|
expect(getCheckboxRenderer(), paints..drrect()); // empty checkbox
|
|
|
|
await tester.pumpWidget(buildFrame(true));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
getCheckboxRenderer(),
|
|
paints
|
|
..path(color: theme.colorScheme.secondary)
|
|
..path(color: const Color(0xFFFFFFFF)),
|
|
); // checkmark is rendered as a path
|
|
|
|
await tester.pumpWidget(buildFrame(false));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
getCheckboxRenderer(),
|
|
paints..path(color: Colors.transparent),
|
|
); // paint transparent border
|
|
expect(getCheckboxRenderer(), isNot(paints..line())); // null is rendered as a line (a "dash")
|
|
expect(getCheckboxRenderer(), paints..drrect()); // empty checkbox
|
|
|
|
await tester.pumpWidget(buildFrame(null));
|
|
await tester.pumpAndSettle();
|
|
expect(getCheckboxRenderer(), paints..line()); // null is rendered as a line (a "dash")
|
|
|
|
await tester.pumpWidget(buildFrame(true));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
getCheckboxRenderer(),
|
|
paints
|
|
..path(color: theme.colorScheme.secondary)
|
|
..path(color: const Color(0xFFFFFFFF)),
|
|
); // checkmark is rendered as a path
|
|
|
|
await tester.pumpWidget(buildFrame(null));
|
|
await tester.pumpAndSettle();
|
|
expect(getCheckboxRenderer(), paints..line()); // null is rendered as a line (a "dash")
|
|
});
|
|
|
|
testWidgets('Material3 - Checkbox tristate rendering, programmatic transitions', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final ThemeData theme = ThemeData();
|
|
Widget buildFrame(bool? checkboxValue) {
|
|
return Theme(
|
|
data: theme,
|
|
child: Material(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(tristate: true, value: checkboxValue, onChanged: (bool? value) {});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
RenderBox getCheckboxRenderer() {
|
|
return tester.renderObject<RenderBox>(find.byType(Checkbox));
|
|
}
|
|
|
|
await tester.pumpWidget(buildFrame(false));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
getCheckboxRenderer(),
|
|
paints..path(color: Colors.transparent),
|
|
); // paint transparent border
|
|
expect(getCheckboxRenderer(), isNot(paints..line())); // null is rendered as a line (a "dash")
|
|
expect(getCheckboxRenderer(), paints..drrect()); // empty checkbox
|
|
|
|
await tester.pumpWidget(buildFrame(true));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
getCheckboxRenderer(),
|
|
paints
|
|
..path(color: theme.colorScheme.primary)
|
|
..path(color: theme.colorScheme.onPrimary),
|
|
); // checkmark is rendered as a path
|
|
|
|
await tester.pumpWidget(buildFrame(false));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
getCheckboxRenderer(),
|
|
paints..path(color: Colors.transparent),
|
|
); // paint transparent border
|
|
expect(getCheckboxRenderer(), isNot(paints..line())); // null is rendered as a line (a "dash")
|
|
expect(getCheckboxRenderer(), paints..drrect()); // empty checkbox
|
|
|
|
await tester.pumpWidget(buildFrame(null));
|
|
await tester.pumpAndSettle();
|
|
expect(getCheckboxRenderer(), paints..line()); // null is rendered as a line (a "dash")
|
|
|
|
await tester.pumpWidget(buildFrame(true));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
getCheckboxRenderer(),
|
|
paints
|
|
..path(color: theme.colorScheme.primary)
|
|
..path(color: theme.colorScheme.onPrimary),
|
|
); // checkmark is rendered as a path
|
|
|
|
await tester.pumpWidget(buildFrame(null));
|
|
await tester.pumpAndSettle();
|
|
expect(getCheckboxRenderer(), paints..line()); // null is rendered as a line (a "dash")
|
|
});
|
|
|
|
testWidgets('Material2 - Checkbox color rendering', (WidgetTester tester) async {
|
|
ThemeData theme = ThemeData(useMaterial3: false);
|
|
const Color borderColor = Color(0xff2196f3);
|
|
Color checkColor = const Color(0xffFFFFFF);
|
|
Color activeColor;
|
|
|
|
Widget buildFrame({Color? activeColor, Color? checkColor, ThemeData? themeData}) {
|
|
return Material(
|
|
child: Theme(
|
|
data: themeData ?? theme,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(
|
|
value: true,
|
|
activeColor: activeColor,
|
|
checkColor: checkColor,
|
|
onChanged: (bool? value) {},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
RenderBox getCheckboxRenderer() {
|
|
return tester.renderObject<RenderBox>(find.byType(Checkbox));
|
|
}
|
|
|
|
await tester.pumpWidget(buildFrame(checkColor: checkColor));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
getCheckboxRenderer(),
|
|
paints
|
|
..path(color: borderColor)
|
|
..path(color: checkColor),
|
|
); // paints's color is 0xFFFFFFFF (default color)
|
|
|
|
checkColor = const Color(0xFF000000);
|
|
|
|
await tester.pumpWidget(buildFrame(checkColor: checkColor));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
getCheckboxRenderer(),
|
|
paints
|
|
..path(color: borderColor)
|
|
..path(color: checkColor),
|
|
); // paints's color is 0xFF000000 (params)
|
|
|
|
activeColor = const Color(0xFF00FF00);
|
|
|
|
final ColorScheme colorScheme = const ColorScheme.light().copyWith(secondary: activeColor);
|
|
theme = theme.copyWith(colorScheme: colorScheme);
|
|
await tester.pumpWidget(buildFrame(themeData: theme));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
getCheckboxRenderer(),
|
|
paints..path(color: activeColor),
|
|
); // paints's color is 0xFF00FF00 (theme)
|
|
|
|
activeColor = const Color(0xFF000000);
|
|
|
|
await tester.pumpWidget(buildFrame(activeColor: activeColor));
|
|
await tester.pumpAndSettle();
|
|
expect(getCheckboxRenderer(), paints..path(color: activeColor));
|
|
});
|
|
|
|
testWidgets('Material3 - Checkbox color rendering', (WidgetTester tester) async {
|
|
ThemeData theme = ThemeData();
|
|
const Color borderColor = Color(0xFF6750A4);
|
|
Color checkColor = const Color(0xffFFFFFF);
|
|
Color activeColor;
|
|
|
|
Widget buildFrame({Color? activeColor, Color? checkColor, ThemeData? themeData}) {
|
|
return Material(
|
|
child: Theme(
|
|
data: themeData ?? theme,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(
|
|
value: true,
|
|
activeColor: activeColor,
|
|
checkColor: checkColor,
|
|
onChanged: (bool? value) {},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
RenderBox getCheckboxRenderer() {
|
|
return tester.renderObject<RenderBox>(find.byType(Checkbox));
|
|
}
|
|
|
|
await tester.pumpWidget(buildFrame(checkColor: checkColor));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
getCheckboxRenderer(),
|
|
paints
|
|
..path(color: borderColor)
|
|
..path(color: checkColor),
|
|
); // paints's color is 0xFFFFFFFF (default color)
|
|
|
|
checkColor = const Color(0xFF000000);
|
|
|
|
await tester.pumpWidget(buildFrame(checkColor: checkColor));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
getCheckboxRenderer(),
|
|
paints
|
|
..path(color: borderColor)
|
|
..path(color: checkColor),
|
|
); // paints's color is 0xFF000000 (params)
|
|
|
|
activeColor = const Color(0xFF00FF00);
|
|
|
|
final ColorScheme colorScheme = const ColorScheme.light().copyWith(primary: activeColor);
|
|
theme = theme.copyWith(colorScheme: colorScheme);
|
|
await tester.pumpWidget(buildFrame(themeData: theme));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
getCheckboxRenderer(),
|
|
paints..path(color: activeColor),
|
|
); // paints's color is 0xFF00FF00 (theme)
|
|
|
|
activeColor = const Color(0xFF000000);
|
|
|
|
await tester.pumpWidget(buildFrame(activeColor: activeColor));
|
|
await tester.pumpAndSettle();
|
|
expect(getCheckboxRenderer(), paints..path(color: activeColor));
|
|
});
|
|
|
|
testWidgets('Material2 - Checkbox is focusable and has correct focus color', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final FocusNode focusNode = FocusNode(debugLabel: 'Checkbox');
|
|
addTearDown(focusNode.dispose);
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
bool? value = true;
|
|
Widget buildApp({bool enabled = true}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(
|
|
value: value,
|
|
onChanged: enabled
|
|
? (bool? newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
}
|
|
: null,
|
|
focusColor: Colors.orange[500],
|
|
autofocus: true,
|
|
focusNode: focusNode,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints
|
|
..circle(color: Colors.orange[500])
|
|
..path(color: const Color(0xff2196f3))
|
|
..path(color: Colors.white),
|
|
);
|
|
|
|
// Check the false value.
|
|
value = false;
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints
|
|
..circle(color: Colors.orange[500])
|
|
..drrect(
|
|
color: const Color(0x8a000000),
|
|
outer: RRect.fromLTRBR(15.0, 15.0, 33.0, 33.0, const Radius.circular(1.0)),
|
|
inner: RRect.fromLTRBR(17.0, 17.0, 31.0, 31.0, Radius.zero),
|
|
),
|
|
);
|
|
|
|
// Check what happens when disabled.
|
|
value = false;
|
|
await tester.pumpWidget(buildApp(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isFalse);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..drrect(
|
|
color: const Color(0x61000000),
|
|
outer: RRect.fromLTRBR(15.0, 15.0, 33.0, 33.0, const Radius.circular(1.0)),
|
|
inner: RRect.fromLTRBR(17.0, 17.0, 31.0, 31.0, Radius.zero),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Material3 - Checkbox is focusable and has correct focus color', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final FocusNode focusNode = FocusNode(debugLabel: 'Checkbox');
|
|
addTearDown(focusNode.dispose);
|
|
final ThemeData theme = ThemeData();
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
bool? value = true;
|
|
Widget buildApp({bool enabled = true}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(
|
|
value: value,
|
|
onChanged: enabled
|
|
? (bool? newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
}
|
|
: null,
|
|
focusColor: Colors.orange[500],
|
|
autofocus: true,
|
|
focusNode: focusNode,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints
|
|
..circle(color: Colors.orange[500])
|
|
..path(color: theme.colorScheme.primary)
|
|
..path(color: theme.colorScheme.onPrimary),
|
|
);
|
|
|
|
// Check the false value.
|
|
value = false;
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints
|
|
..circle(color: Colors.orange[500])
|
|
..drrect(
|
|
color: theme.colorScheme.onSurface,
|
|
outer: RRect.fromLTRBR(15.0, 15.0, 33.0, 33.0, const Radius.circular(2.0)),
|
|
inner: RRect.fromLTRBR(17.0, 17.0, 31.0, 31.0, Radius.zero),
|
|
),
|
|
);
|
|
|
|
// Check what happens when disabled.
|
|
value = false;
|
|
await tester.pumpWidget(buildApp(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isFalse);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..drrect(
|
|
color: theme.colorScheme.onSurface.withOpacity(0.38),
|
|
outer: RRect.fromLTRBR(15.0, 15.0, 33.0, 33.0, const Radius.circular(2.0)),
|
|
inner: RRect.fromLTRBR(17.0, 17.0, 31.0, 31.0, Radius.zero),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Checkbox with splash radius set', (WidgetTester tester) async {
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
const double splashRadius = 30;
|
|
Widget buildApp() {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(
|
|
value: false,
|
|
onChanged: (bool? newValue) {},
|
|
focusColor: Colors.orange[500],
|
|
autofocus: true,
|
|
splashRadius: splashRadius,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(color: Colors.orange[500], radius: splashRadius),
|
|
);
|
|
});
|
|
|
|
testWidgets('Checkbox starts the splash in center, even when tap is on the corner', (
|
|
WidgetTester tester,
|
|
) async {
|
|
Widget buildApp() {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(value: false, onChanged: (bool? newValue) {});
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
final Offset checkboxTopLeftGlobal = tester.getTopLeft(find.byType(Checkbox));
|
|
final Offset checkboxCenterGlobal = tester.getCenter(find.byType(Checkbox));
|
|
final Offset checkboxCenterLocal = checkboxCenterGlobal - checkboxTopLeftGlobal;
|
|
final TestGesture gesture = await tester.startGesture(checkboxTopLeftGlobal);
|
|
await tester.pump();
|
|
// Wait for the splash to be drawn, but not long enough for it to animate towards the center, since
|
|
// we want to catch it in its starting position.
|
|
await tester.pump(const Duration(milliseconds: 1));
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(x: checkboxCenterLocal.dx, y: checkboxCenterLocal.dy),
|
|
);
|
|
|
|
// Finish gesture to release resources.
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('Material2 - Checkbox can be hovered and has correct hover color', (
|
|
WidgetTester tester,
|
|
) async {
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
bool? value = true;
|
|
final ThemeData theme = ThemeData(useMaterial3: false);
|
|
Widget buildApp({bool enabled = true}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(
|
|
value: value,
|
|
onChanged: enabled
|
|
? (bool? newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
}
|
|
: null,
|
|
hoverColor: Colors.orange[500],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints
|
|
..path(color: const Color(0xff2196f3))
|
|
..path(color: const Color(0xffffffff), style: PaintingStyle.stroke, strokeWidth: 2.0),
|
|
);
|
|
|
|
// Start hovering
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(tester.getCenter(find.byType(Checkbox)));
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints
|
|
..circle(color: Colors.orange[500])
|
|
..path(color: const Color(0xff2196f3))
|
|
..path(color: const Color(0xffffffff), style: PaintingStyle.stroke, strokeWidth: 2.0),
|
|
);
|
|
|
|
// Check what happens when disabled.
|
|
await tester.pumpWidget(buildApp(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints
|
|
..path(color: const Color(0x61000000))
|
|
..path(color: const Color(0xffffffff), style: PaintingStyle.stroke, strokeWidth: 2.0),
|
|
);
|
|
});
|
|
|
|
testWidgets('Material3 - Checkbox can be hovered and has correct hover color', (
|
|
WidgetTester tester,
|
|
) async {
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
bool? value = true;
|
|
final ThemeData theme = ThemeData();
|
|
Widget buildApp({bool enabled = true}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(
|
|
value: value,
|
|
onChanged: enabled
|
|
? (bool? newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
}
|
|
: null,
|
|
hoverColor: Colors.orange[500],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints
|
|
..path(color: const Color(0xff6750a4))
|
|
..path(color: theme.colorScheme.onPrimary, style: PaintingStyle.stroke, strokeWidth: 2.0),
|
|
);
|
|
|
|
// Start hovering
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(tester.getCenter(find.byType(Checkbox)));
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints
|
|
..circle(color: Colors.orange[500])
|
|
..path(color: const Color(0xff6750a4))
|
|
..path(color: theme.colorScheme.onPrimary, style: PaintingStyle.stroke, strokeWidth: 2.0),
|
|
);
|
|
|
|
// Check what happens when disabled.
|
|
await tester.pumpWidget(buildApp(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints
|
|
..path(color: theme.colorScheme.onSurface.withOpacity(0.38))
|
|
..path(color: theme.colorScheme.surface, style: PaintingStyle.stroke, strokeWidth: 2.0),
|
|
);
|
|
});
|
|
|
|
testWidgets('Checkbox can be toggled by keyboard shortcuts', (WidgetTester tester) async {
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
bool? value = true;
|
|
Widget buildApp({bool enabled = true}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(
|
|
value: value,
|
|
onChanged: enabled
|
|
? (bool? newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
}
|
|
: null,
|
|
focusColor: Colors.orange[500],
|
|
autofocus: true,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
|
|
await tester.pumpAndSettle();
|
|
// On web, checkboxes don't respond to the enter key.
|
|
expect(value, kIsWeb ? isTrue : isFalse);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
|
|
await tester.pumpAndSettle();
|
|
expect(value, isTrue);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.space);
|
|
await tester.pumpAndSettle();
|
|
expect(value, isFalse);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.space);
|
|
await tester.pumpAndSettle();
|
|
expect(value, isTrue);
|
|
});
|
|
|
|
testWidgets(
|
|
'Material3 - Checkbox visual density cannot be overriden by ThemeData.visualDensity',
|
|
(WidgetTester tester) async {
|
|
const Key key = Key('test');
|
|
Widget buldCheckbox() {
|
|
return MaterialApp(
|
|
theme: theme.copyWith(visualDensity: VisualDensity.compact),
|
|
home: Material(
|
|
child: Center(
|
|
child: Checkbox(key: key, value: true, onChanged: (bool? value) {}),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buldCheckbox());
|
|
await tester.pumpAndSettle();
|
|
final RenderBox box = tester.renderObject(find.byKey(key));
|
|
expect(box.size, equals(const Size(48, 48)));
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'Material3 - Checkbox with MaterialTapTargetSize.padded meets Material Guidelines on desktop',
|
|
(WidgetTester tester) async {
|
|
const Key key = Key('test');
|
|
Widget buldCheckbox() {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: Checkbox(
|
|
key: key,
|
|
materialTapTargetSize: MaterialTapTargetSize.padded,
|
|
value: true,
|
|
onChanged: (bool? value) {},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buldCheckbox());
|
|
await tester.pumpAndSettle();
|
|
final RenderBox box = tester.renderObject(find.byKey(key));
|
|
expect(box.size, equals(const Size(48, 48)));
|
|
},
|
|
variant: TargetPlatformVariant.desktop(),
|
|
);
|
|
|
|
testWidgets('Checkbox responds to density changes', (WidgetTester tester) async {
|
|
const Key key = Key('test');
|
|
Future<void> buildTest({VisualDensity? visualDensity}) async {
|
|
return tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: Checkbox(
|
|
visualDensity: visualDensity,
|
|
key: key,
|
|
onChanged: (bool? value) {},
|
|
value: true,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Test the default visual density.
|
|
await buildTest();
|
|
await tester.pumpAndSettle();
|
|
final RenderBox box = tester.renderObject(find.byKey(key));
|
|
expect(box.size, equals(const Size(48, 48)));
|
|
|
|
await buildTest(visualDensity: VisualDensity.standard);
|
|
await tester.pumpAndSettle();
|
|
expect(box.size, equals(const Size(48, 48)));
|
|
|
|
await buildTest(visualDensity: VisualDensity.compact);
|
|
await tester.pumpAndSettle();
|
|
expect(box.size, equals(const Size(40, 40)));
|
|
|
|
await buildTest(visualDensity: const VisualDensity(horizontal: 3.0, vertical: 3.0));
|
|
await tester.pumpAndSettle();
|
|
expect(box.size, equals(const Size(60, 60)));
|
|
|
|
await buildTest(visualDensity: const VisualDensity(horizontal: -3.0, vertical: -3.0));
|
|
await tester.pumpAndSettle();
|
|
expect(box.size, equals(const Size(36, 36)));
|
|
|
|
await buildTest(visualDensity: const VisualDensity(horizontal: 3.0, vertical: -3.0));
|
|
await tester.pumpAndSettle();
|
|
expect(box.size, equals(const Size(60, 36)));
|
|
});
|
|
|
|
testWidgets('Checkbox stops hover animation when removed from the tree.', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const Key checkboxKey = Key('checkbox');
|
|
bool? checkboxVal = true;
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (_, StateSetter setState) => Checkbox(
|
|
key: checkboxKey,
|
|
value: checkboxVal,
|
|
onChanged: (bool? newValue) => setState(() {
|
|
checkboxVal = newValue;
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.byKey(checkboxKey), findsOneWidget);
|
|
final Offset checkboxCenter = tester.getCenter(find.byKey(checkboxKey));
|
|
final TestGesture testGesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await testGesture.moveTo(checkboxCenter);
|
|
|
|
await tester.pump(); // start animation
|
|
await tester.pump(
|
|
const Duration(milliseconds: 25),
|
|
); // hover animation duration is 50 ms. It is half-way.
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Material(child: Center(child: Container())),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Hover animation should not trigger an exception when the checkbox is removed
|
|
// before the hover animation should complete.
|
|
expect(tester.takeException(), isNull);
|
|
|
|
await testGesture.removePointer();
|
|
});
|
|
|
|
testWidgets('Checkbox changes mouse cursor when hovered', (WidgetTester tester) async {
|
|
// Test Checkbox() constructor
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
body: Align(
|
|
alignment: Alignment.topLeft,
|
|
child: Material(
|
|
child: MouseRegion(
|
|
cursor: SystemMouseCursors.forbidden,
|
|
child: Checkbox(
|
|
mouseCursor: SystemMouseCursors.text,
|
|
value: true,
|
|
onChanged: (_) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final TestGesture gesture = await tester.createGesture(
|
|
kind: PointerDeviceKind.mouse,
|
|
pointer: 1,
|
|
);
|
|
await gesture.addPointer(location: tester.getCenter(find.byType(Checkbox)));
|
|
addTearDown(gesture.removePointer);
|
|
|
|
await tester.pump();
|
|
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
SystemMouseCursors.text,
|
|
);
|
|
|
|
// Test default cursor
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
body: Align(
|
|
alignment: Alignment.topLeft,
|
|
child: Material(
|
|
child: MouseRegion(
|
|
cursor: SystemMouseCursors.forbidden,
|
|
child: Checkbox(value: true, onChanged: (_) {}),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
SystemMouseCursors.click,
|
|
);
|
|
|
|
// Test default cursor when disabled
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: const Scaffold(
|
|
body: Align(
|
|
alignment: Alignment.topLeft,
|
|
child: Material(
|
|
child: MouseRegion(
|
|
cursor: SystemMouseCursors.forbidden,
|
|
child: Checkbox(value: true, onChanged: null),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
SystemMouseCursors.basic,
|
|
);
|
|
|
|
// Test cursor when tristate
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: const Scaffold(
|
|
body: Align(
|
|
alignment: Alignment.topLeft,
|
|
child: Material(
|
|
child: MouseRegion(
|
|
cursor: SystemMouseCursors.forbidden,
|
|
child: Checkbox(
|
|
value: null,
|
|
tristate: true,
|
|
onChanged: null,
|
|
mouseCursor: _SelectedGrabMouseCursor(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
SystemMouseCursors.grab,
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('Checkbox fill color resolves in enabled/disabled states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const Color activeEnabledFillColor = Color(0xFF000001);
|
|
const Color activeDisabledFillColor = Color(0xFF000002);
|
|
|
|
Color getFillColor(Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.disabled)) {
|
|
return activeDisabledFillColor;
|
|
}
|
|
return activeEnabledFillColor;
|
|
}
|
|
|
|
final MaterialStateProperty<Color> fillColor = MaterialStateColor.resolveWith(getFillColor);
|
|
|
|
Widget buildFrame({required bool enabled}) {
|
|
return Material(
|
|
child: Theme(
|
|
data: theme,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(
|
|
value: true,
|
|
fillColor: fillColor,
|
|
onChanged: enabled ? (bool? value) {} : null,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
RenderBox getCheckboxRenderer() {
|
|
return tester.renderObject<RenderBox>(find.byType(Checkbox));
|
|
}
|
|
|
|
await tester.pumpWidget(buildFrame(enabled: true));
|
|
await tester.pumpAndSettle();
|
|
expect(getCheckboxRenderer(), paints..path(color: activeEnabledFillColor));
|
|
|
|
await tester.pumpWidget(buildFrame(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(getCheckboxRenderer(), paints..path(color: activeDisabledFillColor));
|
|
});
|
|
|
|
testWidgets('Checkbox fill color resolves in hovered/focused states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final FocusNode focusNode = FocusNode(debugLabel: 'checkbox');
|
|
addTearDown(focusNode.dispose);
|
|
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
const Color hoveredFillColor = Color(0xFF000001);
|
|
const Color focusedFillColor = Color(0xFF000002);
|
|
|
|
Color getFillColor(Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.hovered)) {
|
|
return hoveredFillColor;
|
|
}
|
|
if (states.contains(MaterialState.focused)) {
|
|
return focusedFillColor;
|
|
}
|
|
return Colors.transparent;
|
|
}
|
|
|
|
final MaterialStateProperty<Color> fillColor = MaterialStateColor.resolveWith(getFillColor);
|
|
|
|
Widget buildFrame() {
|
|
return Material(
|
|
child: Theme(
|
|
data: theme,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(
|
|
focusNode: focusNode,
|
|
autofocus: true,
|
|
value: true,
|
|
fillColor: fillColor,
|
|
onChanged: (bool? value) {},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
RenderBox getCheckboxRenderer() {
|
|
return tester.renderObject<RenderBox>(find.byType(Checkbox));
|
|
}
|
|
|
|
await tester.pumpWidget(buildFrame());
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(getCheckboxRenderer(), paints..path(color: focusedFillColor));
|
|
|
|
// Start hovering
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(tester.getCenter(find.byType(Checkbox)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getCheckboxRenderer(), paints..path(color: hoveredFillColor));
|
|
});
|
|
|
|
testWidgets('Checkbox respects shape and side', (WidgetTester tester) async {
|
|
const RoundedRectangleBorder roundedRectangleBorder = RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.all(Radius.circular(5)),
|
|
);
|
|
|
|
const BorderSide side = BorderSide(width: 4, color: Color(0xfff44336));
|
|
|
|
Widget buildApp() {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(
|
|
value: false,
|
|
onChanged: (bool? newValue) {},
|
|
shape: roundedRectangleBorder,
|
|
side: side,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(tester.widget<Checkbox>(find.byType(Checkbox)).shape, roundedRectangleBorder);
|
|
expect(tester.widget<Checkbox>(find.byType(Checkbox)).side, side);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..drrect(
|
|
color: const Color(0xfff44336),
|
|
outer: RRect.fromLTRBR(15.0, 15.0, 33.0, 33.0, const Radius.circular(5)),
|
|
inner: RRect.fromLTRBR(19.0, 19.0, 29.0, 29.0, const Radius.circular(1)),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets(
|
|
'Material2 - Checkbox default overlay color in active/pressed/focused/hovered states',
|
|
(WidgetTester tester) async {
|
|
final FocusNode focusNode = FocusNode(debugLabel: 'Checkbox');
|
|
addTearDown(focusNode.dispose);
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
|
|
final ThemeData theme = ThemeData(useMaterial3: false);
|
|
final ColorScheme colors = theme.colorScheme;
|
|
Widget buildCheckbox({bool active = false, bool focused = false}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
body: Checkbox(
|
|
focusNode: focusNode,
|
|
autofocus: focused,
|
|
value: active,
|
|
onChanged: (_) {},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildCheckbox());
|
|
final TestGesture gesture1 = await tester.startGesture(
|
|
tester.getCenter(find.byType(Checkbox)),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(color: theme.unselectedWidgetColor.withAlpha(kRadialReactionAlpha)),
|
|
reason:
|
|
'Default inactive pressed Checkbox should have overlay color from default fillColor',
|
|
);
|
|
|
|
await tester.pumpWidget(buildCheckbox(active: true));
|
|
final TestGesture gesture2 = await tester.startGesture(
|
|
tester.getCenter(find.byType(Checkbox)),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(color: colors.secondary.withAlpha(kRadialReactionAlpha)),
|
|
reason: 'Default active pressed Checkbox should have overlay color from default fillColor',
|
|
);
|
|
|
|
await tester.pumpWidget(Container()); // reset test
|
|
await tester.pumpWidget(buildCheckbox(focused: true));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(color: theme.focusColor),
|
|
reason: 'Focused Checkbox should use default focused overlay color',
|
|
);
|
|
|
|
await tester.pumpWidget(Container()); // reset test
|
|
await tester.pumpWidget(buildCheckbox());
|
|
final TestGesture gesture3 = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture3.addPointer();
|
|
addTearDown(gesture3.removePointer);
|
|
await gesture3.moveTo(tester.getCenter(find.byType(Checkbox)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(color: theme.hoverColor),
|
|
reason: 'Hovered Checkbox should use default hovered overlay color',
|
|
);
|
|
|
|
// Finish gestures to release resources.
|
|
await gesture1.up();
|
|
await gesture2.up();
|
|
await tester.pumpAndSettle();
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'Material3 - Checkbox default overlay color in active/pressed/focused/hovered states',
|
|
(WidgetTester tester) async {
|
|
final FocusNode focusNode = FocusNode(debugLabel: 'Checkbox');
|
|
addTearDown(focusNode.dispose);
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
|
|
final ThemeData theme = ThemeData();
|
|
final ColorScheme colors = theme.colorScheme;
|
|
Widget buildCheckbox({bool active = false, bool focused = false}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
body: Checkbox(
|
|
focusNode: focusNode,
|
|
autofocus: focused,
|
|
value: active,
|
|
onChanged: (_) {},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildCheckbox());
|
|
final TestGesture gesture1 = await tester.startGesture(
|
|
tester.getCenter(find.byType(Checkbox)),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(color: colors.primary.withOpacity(0.1)),
|
|
reason:
|
|
'Default inactive pressed Checkbox should have overlay color from default fillColor',
|
|
);
|
|
|
|
await tester.pumpWidget(buildCheckbox(active: true));
|
|
final TestGesture gesture2 = await tester.startGesture(
|
|
tester.getCenter(find.byType(Checkbox)),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(color: colors.onSurface.withOpacity(0.1)),
|
|
reason: 'Default active pressed Checkbox should have overlay color from default fillColor',
|
|
);
|
|
|
|
await tester.pumpWidget(Container()); // reset test
|
|
await tester.pumpWidget(buildCheckbox(focused: true));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(color: colors.onSurface.withOpacity(0.1)),
|
|
reason: 'Focused Checkbox should use default focused overlay color',
|
|
);
|
|
|
|
await tester.pumpWidget(Container()); // reset test
|
|
await tester.pumpWidget(buildCheckbox());
|
|
final TestGesture gesture3 = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture3.addPointer();
|
|
addTearDown(gesture3.removePointer);
|
|
await gesture3.moveTo(tester.getCenter(find.byType(Checkbox)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(color: colors.onSurface.withOpacity(0.08)),
|
|
reason: 'Hovered Checkbox should use default hovered overlay color',
|
|
);
|
|
|
|
// Finish gestures to release resources.
|
|
await gesture1.up();
|
|
await gesture2.up();
|
|
await tester.pumpAndSettle();
|
|
},
|
|
);
|
|
|
|
testWidgets('Checkbox overlay color resolves in active/pressed/focused/hovered states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final FocusNode focusNode = FocusNode(debugLabel: 'Checkbox');
|
|
addTearDown(focusNode.dispose);
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
|
|
const Color fillColor = Color(0xFF000000);
|
|
const Color activePressedOverlayColor = Color(0xFF000001);
|
|
const Color inactivePressedOverlayColor = Color(0xFF000002);
|
|
const Color hoverOverlayColor = Color(0xFF000003);
|
|
const Color focusOverlayColor = Color(0xFF000004);
|
|
const Color hoverColor = Color(0xFF000005);
|
|
const Color focusColor = Color(0xFF000006);
|
|
|
|
Color? getOverlayColor(Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.pressed)) {
|
|
if (states.contains(MaterialState.selected)) {
|
|
return activePressedOverlayColor;
|
|
}
|
|
return inactivePressedOverlayColor;
|
|
}
|
|
if (states.contains(MaterialState.hovered)) {
|
|
return hoverOverlayColor;
|
|
}
|
|
if (states.contains(MaterialState.focused)) {
|
|
return focusOverlayColor;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
const double splashRadius = 24.0;
|
|
|
|
Widget buildCheckbox({bool active = false, bool focused = false, bool useOverlay = true}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
body: Checkbox(
|
|
focusNode: focusNode,
|
|
autofocus: focused,
|
|
value: active,
|
|
onChanged: (_) {},
|
|
fillColor: const MaterialStatePropertyAll<Color>(fillColor),
|
|
overlayColor: useOverlay ? MaterialStateProperty.resolveWith(getOverlayColor) : null,
|
|
hoverColor: hoverColor,
|
|
focusColor: focusColor,
|
|
splashRadius: splashRadius,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildCheckbox(useOverlay: false));
|
|
final TestGesture gesture1 = await tester.startGesture(tester.getCenter(find.byType(Checkbox)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(color: fillColor.withAlpha(kRadialReactionAlpha), radius: splashRadius),
|
|
reason: 'Default inactive pressed Checkbox should have overlay color from fillColor',
|
|
);
|
|
|
|
await tester.pumpWidget(buildCheckbox(active: true, useOverlay: false));
|
|
final TestGesture gesture2 = await tester.startGesture(tester.getCenter(find.byType(Checkbox)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(color: fillColor.withAlpha(kRadialReactionAlpha), radius: splashRadius),
|
|
reason: 'Default active pressed Checkbox should have overlay color from fillColor',
|
|
);
|
|
|
|
await tester.pumpWidget(buildCheckbox());
|
|
final TestGesture gesture3 = await tester.startGesture(tester.getCenter(find.byType(Checkbox)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(color: inactivePressedOverlayColor, radius: splashRadius),
|
|
reason: 'Inactive pressed Checkbox should have overlay color: $inactivePressedOverlayColor',
|
|
);
|
|
|
|
await tester.pumpWidget(buildCheckbox(active: true));
|
|
final TestGesture gesture4 = await tester.startGesture(tester.getCenter(find.byType(Checkbox)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(color: activePressedOverlayColor, radius: splashRadius),
|
|
reason: 'Active pressed Checkbox should have overlay color: $activePressedOverlayColor',
|
|
);
|
|
|
|
await tester.pumpWidget(Container()); // reset test
|
|
await tester.pumpWidget(buildCheckbox(focused: true));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(color: focusOverlayColor, radius: splashRadius),
|
|
reason: 'Focused Checkbox should use overlay color $focusOverlayColor over $focusColor',
|
|
);
|
|
|
|
// Start hovering
|
|
final TestGesture gesture5 = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture5.addPointer();
|
|
addTearDown(gesture5.removePointer);
|
|
await gesture5.moveTo(tester.getCenter(find.byType(Checkbox)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(color: hoverOverlayColor, radius: splashRadius),
|
|
reason: 'Hovered Checkbox should use overlay color $hoverOverlayColor over $hoverColor',
|
|
);
|
|
|
|
// Finish gestures to release resources.
|
|
await gesture1.up();
|
|
await gesture2.up();
|
|
await gesture3.up();
|
|
await gesture4.up();
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('Tristate Checkbox overlay color resolves in pressed active/inactive states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const Color activePressedOverlayColor = Color(0xFF000001);
|
|
const Color inactivePressedOverlayColor = Color(0xFF000002);
|
|
|
|
Color? getOverlayColor(Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.pressed)) {
|
|
if (states.contains(MaterialState.selected)) {
|
|
return activePressedOverlayColor;
|
|
}
|
|
return inactivePressedOverlayColor;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
const double splashRadius = 24.0;
|
|
TestGesture gesture;
|
|
bool? value = false;
|
|
|
|
Widget buildTristateCheckbox() {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
body: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(
|
|
value: value,
|
|
tristate: true,
|
|
onChanged: (bool? v) {
|
|
setState(() {
|
|
value = v;
|
|
});
|
|
},
|
|
overlayColor: MaterialStateProperty.resolveWith(getOverlayColor),
|
|
splashRadius: splashRadius,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// The checkbox is inactive.
|
|
await tester.pumpWidget(buildTristateCheckbox());
|
|
gesture = await tester.press(find.byType(Checkbox));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(value, false);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(color: inactivePressedOverlayColor, radius: splashRadius),
|
|
reason: 'Inactive pressed Checkbox should have overlay color: $inactivePressedOverlayColor',
|
|
);
|
|
|
|
// The checkbox is active.
|
|
await gesture.up();
|
|
gesture = await tester.press(find.byType(Checkbox));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(value, true);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(color: activePressedOverlayColor, radius: splashRadius),
|
|
reason: 'Active pressed Checkbox should have overlay color: $activePressedOverlayColor',
|
|
);
|
|
|
|
// The checkbox is active in tri-state.
|
|
await gesture.up();
|
|
gesture = await tester.press(find.byType(Checkbox));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(value, null);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(color: activePressedOverlayColor, radius: splashRadius),
|
|
reason:
|
|
'Active (tristate) pressed Checkbox should have overlay color: $activePressedOverlayColor',
|
|
);
|
|
|
|
// The checkbox is inactive again.
|
|
await gesture.up();
|
|
gesture = await tester.press(find.byType(Checkbox));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(value, false);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..circle(color: inactivePressedOverlayColor, radius: splashRadius),
|
|
reason: 'Inactive pressed Checkbox should have overlay color: $inactivePressedOverlayColor',
|
|
);
|
|
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('Do not crash when widget disappears while pointer is down', (
|
|
WidgetTester tester,
|
|
) async {
|
|
Widget buildCheckbox(bool show) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(child: show ? Checkbox(value: true, onChanged: (_) {}) : Container()),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildCheckbox(true));
|
|
final Offset center = tester.getCenter(find.byType(Checkbox));
|
|
// Put a pointer down on the screen.
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
await tester.pump();
|
|
// While the pointer is down, the widget disappears.
|
|
await tester.pumpWidget(buildCheckbox(false));
|
|
expect(find.byType(Checkbox), findsNothing);
|
|
// Release pointer after widget disappeared.
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('Checkbox BorderSide side only applies when unselected in M2', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const Color borderColor = Color(0xfff44336);
|
|
const Color activeColor = Color(0xff123456);
|
|
const BorderSide side = BorderSide(width: 4, color: borderColor);
|
|
|
|
Widget buildApp({bool? value, bool enabled = true}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Material(
|
|
child: Center(
|
|
child: Checkbox(
|
|
value: value,
|
|
tristate: value == null,
|
|
activeColor: activeColor,
|
|
onChanged: enabled ? (bool? newValue) {} : null,
|
|
side: side,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
RenderBox getCheckboxRenderer() {
|
|
return tester.renderObject<RenderBox>(find.byType(Checkbox));
|
|
}
|
|
|
|
void expectBorder() {
|
|
expect(
|
|
getCheckboxRenderer(),
|
|
paints..drrect(
|
|
color: borderColor,
|
|
outer: RRect.fromLTRBR(15, 15, 33, 33, const Radius.circular(1)),
|
|
inner: RRect.fromLTRBR(19, 19, 29, 29, Radius.zero),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Checkbox is unselected, so the specified BorderSide appears.
|
|
|
|
await tester.pumpWidget(buildApp(value: false));
|
|
await tester.pumpAndSettle();
|
|
expectBorder();
|
|
|
|
await tester.pumpWidget(buildApp(value: false, enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expectBorder();
|
|
|
|
// Checkbox is selected/indeterminate, so the specified BorderSide is transparent
|
|
|
|
await tester.pumpWidget(buildApp(value: true));
|
|
await tester.pumpAndSettle();
|
|
expect(getCheckboxRenderer(), paints..drrect(color: Colors.transparent));
|
|
expect(getCheckboxRenderer(), paints..path(color: activeColor)); // checkbox fill
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expect(getCheckboxRenderer(), paints..drrect(color: Colors.transparent));
|
|
expect(getCheckboxRenderer(), paints..path(color: activeColor)); // checkbox fill
|
|
});
|
|
|
|
testWidgets('Material2 - Checkbox MaterialStateBorderSide applies unconditionally', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const Color borderColor = Color(0xfff44336);
|
|
const BorderSide side = BorderSide(width: 4, color: borderColor);
|
|
final ThemeData theme = ThemeData(useMaterial3: false);
|
|
|
|
Widget buildApp({bool? value, bool enabled = true}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: Checkbox(
|
|
value: value,
|
|
tristate: value == null,
|
|
onChanged: enabled ? (bool? newValue) {} : null,
|
|
side: MaterialStateBorderSide.resolveWith((Set<MaterialState> states) => side),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void expectBorder() {
|
|
expect(
|
|
tester.renderObject<RenderBox>(find.byType(Checkbox)),
|
|
paints..drrect(
|
|
color: borderColor,
|
|
outer: RRect.fromLTRBR(15, 15, 33, 33, const Radius.circular(1)),
|
|
inner: RRect.fromLTRBR(19, 19, 29, 29, Radius.zero),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp(value: false));
|
|
await tester.pumpAndSettle();
|
|
expectBorder();
|
|
|
|
await tester.pumpWidget(buildApp(value: false, enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expectBorder();
|
|
|
|
await tester.pumpWidget(buildApp(value: true));
|
|
await tester.pumpAndSettle();
|
|
expectBorder();
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expectBorder();
|
|
});
|
|
|
|
testWidgets('Material3 - Checkbox MaterialStateBorderSide applies unconditionally', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const Color borderColor = Color(0xfff44336);
|
|
const BorderSide side = BorderSide(width: 4, color: borderColor);
|
|
final ThemeData theme = ThemeData();
|
|
|
|
Widget buildApp({bool? value, bool enabled = true}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: Checkbox(
|
|
value: value,
|
|
tristate: value == null,
|
|
onChanged: enabled ? (bool? newValue) {} : null,
|
|
side: MaterialStateBorderSide.resolveWith((Set<MaterialState> states) => side),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void expectBorder() {
|
|
expect(
|
|
tester.renderObject<RenderBox>(find.byType(Checkbox)),
|
|
paints..drrect(
|
|
color: borderColor,
|
|
outer: RRect.fromLTRBR(15, 15, 33, 33, const Radius.circular(2)),
|
|
inner: RRect.fromLTRBR(19, 19, 29, 29, Radius.zero),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp(value: false));
|
|
await tester.pumpAndSettle();
|
|
expectBorder();
|
|
|
|
await tester.pumpWidget(buildApp(value: false, enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expectBorder();
|
|
|
|
await tester.pumpWidget(buildApp(value: true));
|
|
await tester.pumpAndSettle();
|
|
expectBorder();
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expectBorder();
|
|
});
|
|
|
|
testWidgets('disabled checkbox shows tooltip', (WidgetTester tester) async {
|
|
const String longPressTooltip = 'long press tooltip';
|
|
const String tapTooltip = 'tap tooltip';
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Material(
|
|
child: Tooltip(message: longPressTooltip, child: Checkbox(value: true, onChanged: null)),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Default tooltip shows up after long pressed.
|
|
final Finder tooltip0 = find.byType(Tooltip);
|
|
expect(find.text(longPressTooltip), findsNothing);
|
|
|
|
await tester.tap(tooltip0);
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
expect(find.text(longPressTooltip), findsNothing);
|
|
|
|
final TestGesture gestureLongPress = await tester.startGesture(tester.getCenter(tooltip0));
|
|
await tester.pump();
|
|
await tester.pump(kLongPressTimeout);
|
|
await gestureLongPress.up();
|
|
await tester.pump();
|
|
|
|
expect(find.text(longPressTooltip), findsOneWidget);
|
|
|
|
// Tooltip shows up after tapping when set triggerMode to TooltipTriggerMode.tap.
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Material(
|
|
child: Tooltip(
|
|
triggerMode: TooltipTriggerMode.tap,
|
|
message: tapTooltip,
|
|
child: Checkbox(value: true, onChanged: null),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pump(const Duration(days: 1));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(tapTooltip), findsNothing);
|
|
expect(find.text(longPressTooltip), findsNothing);
|
|
|
|
final Finder tooltip1 = find.byType(Tooltip);
|
|
await tester.tap(tooltip1);
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
expect(find.text(tapTooltip), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Material3 - Checkbox has default error color when isError is set to true', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final FocusNode focusNode = FocusNode(debugLabel: 'Checkbox');
|
|
addTearDown(focusNode.dispose);
|
|
final ThemeData themeData = ThemeData();
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
bool? value = true;
|
|
Widget buildApp({bool autoFocus = true}) {
|
|
return MaterialApp(
|
|
theme: themeData,
|
|
home: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(
|
|
isError: true,
|
|
value: value,
|
|
onChanged: (bool? newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
autofocus: autoFocus,
|
|
focusNode: focusNode,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Focused
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints
|
|
..circle(color: themeData.colorScheme.error.withOpacity(0.1))
|
|
..path(color: themeData.colorScheme.error)
|
|
..path(color: themeData.colorScheme.onError),
|
|
);
|
|
|
|
// Default color
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpWidget(buildApp(autoFocus: false));
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isFalse);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints
|
|
..path(color: themeData.colorScheme.error)
|
|
..path(color: themeData.colorScheme.onError),
|
|
);
|
|
|
|
// Start hovering
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(tester.getCenter(find.byType(Checkbox)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints
|
|
..circle(color: themeData.colorScheme.error.withOpacity(0.08))
|
|
..path(color: themeData.colorScheme.error),
|
|
);
|
|
|
|
// Start pressing
|
|
final TestGesture gestureLongPress = await tester.startGesture(
|
|
tester.getCenter(find.byType(Checkbox)),
|
|
);
|
|
await tester.pump();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints
|
|
..circle(color: themeData.colorScheme.error.withOpacity(0.1))
|
|
..path(color: themeData.colorScheme.error),
|
|
);
|
|
await gestureLongPress.up();
|
|
await tester.pump();
|
|
});
|
|
|
|
testWidgets('Material3 - Checkbox MaterialStateBorderSide applies in error states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final FocusNode focusNode = FocusNode(debugLabel: 'Checkbox');
|
|
addTearDown(focusNode.dispose);
|
|
final ThemeData themeData = ThemeData();
|
|
const Color borderColor = Color(0xffffeb3b);
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
bool? value = false;
|
|
Widget buildApp({bool autoFocus = true}) {
|
|
return MaterialApp(
|
|
theme: themeData,
|
|
home: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(
|
|
isError: true,
|
|
side: MaterialStateBorderSide.resolveWith((Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.error)) {
|
|
return const BorderSide(color: borderColor, width: 4);
|
|
}
|
|
return const BorderSide(color: Colors.red, width: 2);
|
|
}),
|
|
value: value,
|
|
onChanged: (bool? newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
autofocus: autoFocus,
|
|
focusNode: focusNode,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void expectBorder() {
|
|
expect(
|
|
tester.renderObject<RenderBox>(find.byType(Checkbox)),
|
|
paints..drrect(
|
|
color: borderColor,
|
|
outer: RRect.fromLTRBR(15, 15, 33, 33, const Radius.circular(2)),
|
|
inner: RRect.fromLTRBR(19, 19, 29, 29, Radius.zero),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expectBorder();
|
|
|
|
// Focused
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expectBorder();
|
|
|
|
// Default color
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpWidget(buildApp(autoFocus: false));
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isFalse);
|
|
expectBorder();
|
|
|
|
// Start hovering
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(tester.getCenter(find.byType(Checkbox)));
|
|
await tester.pumpAndSettle();
|
|
expectBorder();
|
|
|
|
// Start pressing
|
|
final TestGesture gestureLongPress = await tester.startGesture(
|
|
tester.getCenter(find.byType(Checkbox)),
|
|
);
|
|
await tester.pump();
|
|
expectBorder();
|
|
await gestureLongPress.up();
|
|
await tester.pump();
|
|
});
|
|
|
|
testWidgets('Material3 - Checkbox has correct default shape', (WidgetTester tester) async {
|
|
final ThemeData themeData = ThemeData();
|
|
|
|
Widget buildApp() {
|
|
return MaterialApp(
|
|
theme: themeData,
|
|
home: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox(value: false, onChanged: (bool? newValue) {});
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
final OutlinedBorder? expectedShape = themeData.checkboxTheme.shape;
|
|
expect(tester.widget<Checkbox>(find.byType(Checkbox)).shape, expectedShape);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Checkbox))),
|
|
paints..drrect(
|
|
outer: RRect.fromLTRBR(15.0, 15.0, 33.0, 33.0, const Radius.circular(2)),
|
|
inner: RRect.fromLTRBR(17.0, 17.0, 31.0, 31.0, Radius.zero),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Checkbox.adaptive shows the correct platform widget', (WidgetTester tester) async {
|
|
Widget buildApp(TargetPlatform platform) {
|
|
return MaterialApp(
|
|
theme: ThemeData(platform: platform),
|
|
home: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Checkbox.adaptive(value: false, onChanged: (bool? newValue) {});
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
for (final TargetPlatform platform in <TargetPlatform>[
|
|
TargetPlatform.iOS,
|
|
TargetPlatform.macOS,
|
|
]) {
|
|
await tester.pumpWidget(buildApp(platform));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byType(CupertinoCheckbox), findsOneWidget);
|
|
}
|
|
|
|
for (final TargetPlatform platform in <TargetPlatform>[
|
|
TargetPlatform.android,
|
|
TargetPlatform.fuchsia,
|
|
TargetPlatform.linux,
|
|
TargetPlatform.windows,
|
|
]) {
|
|
await tester.pumpWidget(buildApp(platform));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byType(CupertinoCheckbox), findsNothing);
|
|
}
|
|
});
|
|
|
|
testWidgets(
|
|
'Checkbox.adaptive respects Checkbox.mouseCursor on iOS/macOS',
|
|
(WidgetTester tester) async {
|
|
Widget buildApp({MouseCursor? mouseCursor}) {
|
|
return MaterialApp(
|
|
home: Material(
|
|
child: Checkbox.adaptive(
|
|
value: true,
|
|
onChanged: (bool? newValue) {},
|
|
mouseCursor: mouseCursor,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
final TestGesture gesture = await tester.createGesture(
|
|
kind: PointerDeviceKind.mouse,
|
|
pointer: 1,
|
|
);
|
|
await gesture.addPointer(location: tester.getCenter(find.byType(CupertinoCheckbox)));
|
|
await tester.pump();
|
|
await gesture.moveTo(tester.getCenter(find.byType(CupertinoCheckbox)));
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
kIsWeb ? SystemMouseCursors.click : SystemMouseCursors.basic,
|
|
);
|
|
|
|
// Test mouse cursor can be configured.
|
|
await tester.pumpWidget(buildApp(mouseCursor: SystemMouseCursors.click));
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
SystemMouseCursors.click,
|
|
);
|
|
|
|
// Test Checkbox.adaptive can resolve a WidgetStateMouseCursor.
|
|
await tester.pumpWidget(buildApp(mouseCursor: const _SelectedGrabMouseCursor()));
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
SystemMouseCursors.grab,
|
|
);
|
|
|
|
await gesture.removePointer();
|
|
},
|
|
variant: const TargetPlatformVariant(<TargetPlatform>{
|
|
TargetPlatform.iOS,
|
|
TargetPlatform.macOS,
|
|
}),
|
|
);
|
|
|
|
testWidgets('Material2 - Checkbox respects fillColor when it is unchecked', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final ThemeData theme = ThemeData(useMaterial3: false);
|
|
const Color activeBackgroundColor = Color(0xff123456);
|
|
const Color inactiveBackgroundColor = Color(0xff654321);
|
|
|
|
Widget buildApp({bool enabled = true}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: Checkbox(
|
|
fillColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.selected)) {
|
|
return activeBackgroundColor;
|
|
}
|
|
return inactiveBackgroundColor;
|
|
}),
|
|
value: false,
|
|
onChanged: enabled ? (bool? newValue) {} : null,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
RenderBox getCheckboxRenderer() {
|
|
return tester.renderObject<RenderBox>(find.byType(Checkbox));
|
|
}
|
|
|
|
// Checkbox is unselected, so the default BorderSide appears and fillColor is checkbox's background color.
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expect(getCheckboxRenderer(), paints..drrect(color: theme.unselectedWidgetColor));
|
|
expect(getCheckboxRenderer(), paints..path(color: inactiveBackgroundColor));
|
|
|
|
await tester.pumpWidget(buildApp(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(getCheckboxRenderer(), paints..drrect(color: theme.disabledColor));
|
|
expect(getCheckboxRenderer(), paints..path(color: inactiveBackgroundColor));
|
|
});
|
|
|
|
testWidgets('Material3 - Checkbox respects fillColor when it is unchecked', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final ThemeData theme = ThemeData();
|
|
const Color activeBackgroundColor = Color(0xff123456);
|
|
const Color inactiveBackgroundColor = Color(0xff654321);
|
|
|
|
Widget buildApp({bool enabled = true}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: Checkbox(
|
|
fillColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.selected)) {
|
|
return activeBackgroundColor;
|
|
}
|
|
return inactiveBackgroundColor;
|
|
}),
|
|
value: false,
|
|
onChanged: enabled ? (bool? newValue) {} : null,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
RenderBox getCheckboxRenderer() {
|
|
return tester.renderObject<RenderBox>(find.byType(Checkbox));
|
|
}
|
|
|
|
// Checkbox is unselected, so the default BorderSide appears and fillColor is checkbox's background color.
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expect(getCheckboxRenderer(), paints..drrect(color: theme.colorScheme.onSurfaceVariant));
|
|
expect(getCheckboxRenderer(), paints..path(color: inactiveBackgroundColor));
|
|
|
|
await tester.pumpWidget(buildApp(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
getCheckboxRenderer(),
|
|
paints..drrect(color: theme.colorScheme.onSurface.withOpacity(0.38)),
|
|
);
|
|
expect(getCheckboxRenderer(), paints..path(color: inactiveBackgroundColor));
|
|
});
|
|
}
|
|
|
|
class _SelectedGrabMouseCursor extends MaterialStateMouseCursor {
|
|
const _SelectedGrabMouseCursor();
|
|
|
|
@override
|
|
MouseCursor resolve(Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.selected)) {
|
|
return SystemMouseCursors.grab;
|
|
}
|
|
return SystemMouseCursors.basic;
|
|
}
|
|
|
|
@override
|
|
String get debugDescription => '_SelectedGrabMouseCursor()';
|
|
}
|