1// Copyright 2017 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'dart:ui'; 6 7import 'package:flutter/rendering.dart'; 8import 'package:flutter/services.dart'; 9import 'package:flutter_test/flutter_test.dart'; 10import 'package:flutter/material.dart'; 11 12import '../rendering/mock_canvas.dart'; 13import '../widgets/semantics_tester.dart'; 14 15void main() { 16 setUp(() { 17 debugResetSemanticsIdCounter(); 18 }); 19 20 testWidgets('Checkbox size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async { 21 await tester.pumpWidget( 22 Theme( 23 data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded), 24 child: Directionality( 25 textDirection: TextDirection.ltr, 26 child: Material( 27 child: Center( 28 child: Checkbox( 29 value: true, 30 onChanged: (bool newValue) { }, 31 ), 32 ), 33 ), 34 ), 35 ), 36 ); 37 38 expect(tester.getSize(find.byType(Checkbox)), const Size(48.0, 48.0)); 39 40 await tester.pumpWidget( 41 Theme( 42 data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap), 43 child: Directionality( 44 textDirection: TextDirection.ltr, 45 child: Material( 46 child: Center( 47 child: Checkbox( 48 value: true, 49 onChanged: (bool newValue) { }, 50 ), 51 ), 52 ), 53 ), 54 ), 55 ); 56 57 expect(tester.getSize(find.byType(Checkbox)), const Size(40.0, 40.0)); 58 }); 59 60 testWidgets('CheckBox semantics', (WidgetTester tester) async { 61 final SemanticsHandle handle = tester.ensureSemantics(); 62 63 await tester.pumpWidget(Material( 64 child: Checkbox( 65 value: false, 66 onChanged: (bool b) { }, 67 ), 68 )); 69 70 expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics( 71 hasCheckedState: true, 72 hasEnabledState: true, 73 isEnabled: true, 74 hasTapAction: true, 75 )); 76 77 await tester.pumpWidget(Material( 78 child: Checkbox( 79 value: true, 80 onChanged: (bool b) { }, 81 ), 82 )); 83 84 expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics( 85 hasCheckedState: true, 86 hasEnabledState: true, 87 isChecked: true, 88 isEnabled: true, 89 hasTapAction: true, 90 )); 91 92 await tester.pumpWidget(const Material( 93 child: Checkbox( 94 value: false, 95 onChanged: null, 96 ), 97 )); 98 99 expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics( 100 hasCheckedState: true, 101 hasEnabledState: true, 102 )); 103 104 await tester.pumpWidget(const Material( 105 child: Checkbox( 106 value: true, 107 onChanged: null, 108 ), 109 )); 110 111 expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics( 112 hasCheckedState: true, 113 hasEnabledState: true, 114 isChecked: true, 115 )); 116 handle.dispose(); 117 }); 118 119 testWidgets('Can wrap CheckBox with Semantics', (WidgetTester tester) async { 120 final SemanticsHandle handle = tester.ensureSemantics(); 121 122 await tester.pumpWidget(Material( 123 child: Semantics( 124 label: 'foo', 125 textDirection: TextDirection.ltr, 126 child: Checkbox( 127 value: false, 128 onChanged: (bool b) { }, 129 ), 130 ), 131 )); 132 133 expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics( 134 label: 'foo', 135 textDirection: TextDirection.ltr, 136 hasCheckedState: true, 137 hasEnabledState: true, 138 isEnabled: true, 139 hasTapAction: true, 140 )); 141 handle.dispose(); 142 }); 143 144 testWidgets('CheckBox tristate: true', (WidgetTester tester) async { 145 bool checkBoxValue; 146 147 await tester.pumpWidget( 148 Material( 149 child: StatefulBuilder( 150 builder: (BuildContext context, StateSetter setState) { 151 return Checkbox( 152 tristate: true, 153 value: checkBoxValue, 154 onChanged: (bool value) { 155 setState(() { 156 checkBoxValue = value; 157 }); 158 }, 159 ); 160 }, 161 ), 162 ), 163 ); 164 165 expect(tester.widget<Checkbox>(find.byType(Checkbox)).value, null); 166 167 await tester.tap(find.byType(Checkbox)); 168 await tester.pumpAndSettle(); 169 expect(checkBoxValue, false); 170 171 await tester.tap(find.byType(Checkbox)); 172 await tester.pumpAndSettle(); 173 expect(checkBoxValue, true); 174 175 await tester.tap(find.byType(Checkbox)); 176 await tester.pumpAndSettle(); 177 expect(checkBoxValue, null); 178 179 checkBoxValue = true; 180 await tester.pumpAndSettle(); 181 expect(checkBoxValue, true); 182 183 checkBoxValue = null; 184 await tester.pumpAndSettle(); 185 expect(checkBoxValue, null); 186 }); 187 188 testWidgets('has semantics for tristate', (WidgetTester tester) async { 189 final SemanticsTester semantics = SemanticsTester(tester); 190 await tester.pumpWidget( 191 Material( 192 child: Checkbox( 193 tristate: true, 194 value: null, 195 onChanged: (bool newValue) { }, 196 ), 197 ), 198 ); 199 200 expect(semantics.nodesWith( 201 flags: <SemanticsFlag>[ 202 SemanticsFlag.hasCheckedState, 203 SemanticsFlag.hasEnabledState, 204 SemanticsFlag.isEnabled, 205 ], 206 actions: <SemanticsAction>[SemanticsAction.tap], 207 ), hasLength(1)); 208 209 await tester.pumpWidget( 210 Material( 211 child: Checkbox( 212 tristate: true, 213 value: true, 214 onChanged: (bool newValue) { }, 215 ), 216 ), 217 ); 218 219 expect(semantics.nodesWith( 220 flags: <SemanticsFlag>[ 221 SemanticsFlag.hasCheckedState, 222 SemanticsFlag.hasEnabledState, 223 SemanticsFlag.isEnabled, 224 SemanticsFlag.isChecked, 225 ], 226 actions: <SemanticsAction>[SemanticsAction.tap], 227 ), hasLength(1)); 228 229 await tester.pumpWidget( 230 Material( 231 child: Checkbox( 232 tristate: true, 233 value: false, 234 onChanged: (bool newValue) { }, 235 ), 236 ), 237 ); 238 239 expect(semantics.nodesWith( 240 flags: <SemanticsFlag>[ 241 SemanticsFlag.hasCheckedState, 242 SemanticsFlag.hasEnabledState, 243 SemanticsFlag.isEnabled, 244 ], 245 actions: <SemanticsAction>[SemanticsAction.tap], 246 ), hasLength(1)); 247 248 semantics.dispose(); 249 }); 250 251 testWidgets('has semantic events', (WidgetTester tester) async { 252 dynamic semanticEvent; 253 bool checkboxValue = false; 254 SystemChannels.accessibility.setMockMessageHandler((dynamic message) async { 255 semanticEvent = message; 256 }); 257 final SemanticsTester semanticsTester = SemanticsTester(tester); 258 259 await tester.pumpWidget( 260 Material( 261 child: StatefulBuilder( 262 builder: (BuildContext context, StateSetter setState) { 263 return Checkbox( 264 value: checkboxValue, 265 onChanged: (bool value) { 266 setState(() { 267 checkboxValue = value; 268 }); 269 }, 270 ); 271 }, 272 ), 273 ), 274 ); 275 276 await tester.tap(find.byType(Checkbox)); 277 final RenderObject object = tester.firstRenderObject(find.byType(Checkbox)); 278 279 expect(checkboxValue, true); 280 expect(semanticEvent, <String, dynamic>{ 281 'type': 'tap', 282 'nodeId': object.debugSemantics.id, 283 'data': <String, dynamic>{}, 284 }); 285 expect(object.debugSemantics.getSemanticsData().hasAction(SemanticsAction.tap), true); 286 287 SystemChannels.accessibility.setMockMessageHandler(null); 288 semanticsTester.dispose(); 289 }); 290 291 testWidgets('CheckBox tristate rendering, programmatic transitions', (WidgetTester tester) async { 292 Widget buildFrame(bool checkboxValue) { 293 return Material( 294 child: StatefulBuilder( 295 builder: (BuildContext context, StateSetter setState) { 296 return Checkbox( 297 tristate: true, 298 value: checkboxValue, 299 onChanged: (bool value) { }, 300 ); 301 }, 302 ), 303 ); 304 } 305 306 RenderToggleable getCheckboxRenderer() { 307 return tester.renderObject<RenderToggleable>(find.byType(Checkbox)); 308 } 309 310 await tester.pumpWidget(buildFrame(false)); 311 await tester.pumpAndSettle(); 312 expect(getCheckboxRenderer(), isNot(paints..path())); // checkmark is rendered as a path 313 expect(getCheckboxRenderer(), isNot(paints..line())); // null is rendered as a line (a "dash") 314 expect(getCheckboxRenderer(), paints..drrect()); // empty checkbox 315 316 await tester.pumpWidget(buildFrame(true)); 317 await tester.pumpAndSettle(); 318 expect(getCheckboxRenderer(), paints..path()); // checkmark is rendered as a path 319 320 await tester.pumpWidget(buildFrame(false)); 321 await tester.pumpAndSettle(); 322 expect(getCheckboxRenderer(), isNot(paints..path())); // checkmark is rendered as a path 323 expect(getCheckboxRenderer(), isNot(paints..line())); // null is rendered as a line (a "dash") 324 expect(getCheckboxRenderer(), paints..drrect()); // empty checkbox 325 326 await tester.pumpWidget(buildFrame(null)); 327 await tester.pumpAndSettle(); 328 expect(getCheckboxRenderer(), paints..line()); // null is rendered as a line (a "dash") 329 330 await tester.pumpWidget(buildFrame(true)); 331 await tester.pumpAndSettle(); 332 expect(getCheckboxRenderer(), paints..path()); // checkmark is rendered as a path 333 334 await tester.pumpWidget(buildFrame(null)); 335 await tester.pumpAndSettle(); 336 expect(getCheckboxRenderer(), paints..line()); // null is rendered as a line (a "dash") 337 }); 338 339 testWidgets('CheckBox color rendering', (WidgetTester tester) async { 340 Widget buildFrame({Color activeColor, Color checkColor, ThemeData themeData}) { 341 return Material( 342 child: Theme( 343 data: themeData ?? ThemeData(), 344 child: StatefulBuilder( 345 builder: (BuildContext context, StateSetter setState) { 346 return Checkbox( 347 value: true, 348 activeColor: activeColor, 349 checkColor: checkColor, 350 onChanged: (bool value) { }, 351 ); 352 }, 353 ), 354 ), 355 ); 356 } 357 358 RenderToggleable getCheckboxRenderer() { 359 return tester.renderObject<RenderToggleable>(find.byType(Checkbox)); 360 } 361 362 await tester.pumpWidget(buildFrame(checkColor: const Color(0xFFFFFFFF))); 363 await tester.pumpAndSettle(); 364 expect(getCheckboxRenderer(), paints..path(color: const Color(0xFFFFFFFF))); // paints's color is 0xFFFFFFFF (default color) 365 366 await tester.pumpWidget(buildFrame(checkColor: const Color(0xFF000000))); 367 await tester.pumpAndSettle(); 368 expect(getCheckboxRenderer(), paints..path(color: const Color(0xFF000000))); // paints's color is 0xFF000000 (params) 369 370 await tester.pumpWidget(buildFrame(themeData: ThemeData(toggleableActiveColor: const Color(0xFF00FF00)))); 371 await tester.pumpAndSettle(); 372 expect(getCheckboxRenderer(), paints..rrect(color: const Color(0xFF00FF00))); // paints's color is 0xFF00FF00 (theme) 373 374 await tester.pumpWidget(buildFrame(activeColor: const Color(0xFF000000))); 375 await tester.pumpAndSettle(); 376 expect(getCheckboxRenderer(), paints..rrect(color: const Color(0xFF000000))); // paints's color is 0xFF000000 (params) 377 }); 378 379} 380