1 | // Copyright 2014 The Flutter 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 | |
5 | import 'package:flutter/gestures.dart'; |
6 | import 'package:flutter/material.dart'; |
7 | import 'package:flutter/rendering.dart'; |
8 | import 'package:flutter_test/flutter_test.dart'; |
9 | import '../widgets/semantics_tester.dart'; |
10 | |
11 | void main() { |
12 | setUp(() { |
13 | debugResetSemanticsIdCounter(); |
14 | }); |
15 | |
16 | testWidgets('MaterialButton defaults' , (WidgetTester tester) async { |
17 | final Finder rawButtonMaterial = find.descendant( |
18 | of: find.byType(MaterialButton), |
19 | matching: find.byType(Material), |
20 | ); |
21 | |
22 | // Enabled MaterialButton |
23 | await tester.pumpWidget( |
24 | Theme( |
25 | data: ThemeData(useMaterial3: false), |
26 | child: Directionality( |
27 | textDirection: TextDirection.ltr, |
28 | child: MaterialButton( |
29 | onPressed: () { }, |
30 | child: const Text('button' ), |
31 | ), |
32 | ), |
33 | ), |
34 | ); |
35 | Material material = tester.widget<Material>(rawButtonMaterial); |
36 | expect(material.animationDuration, const Duration(milliseconds: 200)); |
37 | expect(material.borderOnForeground, true); |
38 | expect(material.borderRadius, null); |
39 | expect(material.clipBehavior, Clip.none); |
40 | expect(material.color, null); |
41 | expect(material.elevation, 2.0); |
42 | expect(material.shadowColor, null); |
43 | expect(material.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0)))); |
44 | expect(material.textStyle!.color, const Color(0xdd000000)); |
45 | expect(material.textStyle!.fontFamily, 'Roboto' ); |
46 | expect(material.textStyle!.fontSize, 14); |
47 | expect(material.textStyle!.fontWeight, FontWeight.w500); |
48 | expect(material.type, MaterialType.transparency); |
49 | |
50 | final Offset center = tester.getCenter(find.byType(MaterialButton)); |
51 | final TestGesture gesture = await tester.startGesture(center); |
52 | await tester.pumpAndSettle(); |
53 | |
54 | // Only elevation changes when enabled and pressed. |
55 | material = tester.widget<Material>(rawButtonMaterial); |
56 | expect(material.animationDuration, const Duration(milliseconds: 200)); |
57 | expect(material.borderOnForeground, true); |
58 | expect(material.borderRadius, null); |
59 | expect(material.clipBehavior, Clip.none); |
60 | expect(material.color, null); |
61 | expect(material.elevation, 8.0); |
62 | expect(material.shadowColor, null); |
63 | expect(material.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0)))); |
64 | expect(material.textStyle!.color, const Color(0xdd000000)); |
65 | expect(material.textStyle!.fontFamily, 'Roboto' ); |
66 | expect(material.textStyle!.fontSize, 14); |
67 | expect(material.textStyle!.fontWeight, FontWeight.w500); |
68 | expect(material.type, MaterialType.transparency); |
69 | |
70 | // Disabled MaterialButton |
71 | await tester.pumpWidget( |
72 | Theme( |
73 | data: ThemeData(useMaterial3: false), |
74 | child: const Directionality( |
75 | textDirection: TextDirection.ltr, |
76 | child: MaterialButton( |
77 | onPressed: null, |
78 | child: Text('button' ), |
79 | ), |
80 | ), |
81 | ), |
82 | ); |
83 | material = tester.widget<Material>(rawButtonMaterial); |
84 | expect(material.animationDuration, const Duration(milliseconds: 200)); |
85 | expect(material.borderOnForeground, true); |
86 | expect(material.borderRadius, null); |
87 | expect(material.clipBehavior, Clip.none); |
88 | expect(material.color, null); |
89 | expect(material.elevation, 0.0); |
90 | expect(material.shadowColor, null); |
91 | expect(material.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0)))); |
92 | expect(material.textStyle!.color, const Color(0x61000000)); |
93 | expect(material.textStyle!.fontFamily, 'Roboto' ); |
94 | expect(material.textStyle!.fontSize, 14); |
95 | expect(material.textStyle!.fontWeight, FontWeight.w500); |
96 | expect(material.type, MaterialType.transparency); |
97 | |
98 | // Finish gesture to release resources. |
99 | await gesture.up(); |
100 | await tester.pumpAndSettle(); |
101 | }); |
102 | |
103 | testWidgets('Does MaterialButton work with hover' , (WidgetTester tester) async { |
104 | const Color hoverColor = Color(0xff001122); |
105 | |
106 | await tester.pumpWidget( |
107 | Directionality( |
108 | textDirection: TextDirection.ltr, |
109 | child: MaterialButton( |
110 | hoverColor: hoverColor, |
111 | onPressed: () { }, |
112 | child: const Text('button' ), |
113 | ), |
114 | ), |
115 | ); |
116 | |
117 | final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); |
118 | await gesture.addPointer(); |
119 | await gesture.moveTo(tester.getCenter(find.byType(MaterialButton))); |
120 | await tester.pumpAndSettle(); |
121 | |
122 | final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures' ); |
123 | expect(inkFeatures, paints..rect(color: hoverColor)); |
124 | }); |
125 | |
126 | testWidgets('Does MaterialButton work with focus' , (WidgetTester tester) async { |
127 | const Color focusColor = Color(0xff001122); |
128 | |
129 | final FocusNode focusNode = FocusNode(debugLabel: 'MaterialButton Node' ); |
130 | await tester.pumpWidget( |
131 | Directionality( |
132 | textDirection: TextDirection.ltr, |
133 | child: MaterialButton( |
134 | focusColor: focusColor, |
135 | focusNode: focusNode, |
136 | onPressed: () { }, |
137 | child: const Text('button' ), |
138 | ), |
139 | ), |
140 | ); |
141 | |
142 | FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; |
143 | focusNode.requestFocus(); |
144 | await tester.pumpAndSettle(); |
145 | |
146 | final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures' ); |
147 | expect(inkFeatures, paints..rect(color: focusColor)); |
148 | |
149 | focusNode.dispose(); |
150 | }); |
151 | |
152 | testWidgets('MaterialButton elevation and colors have proper precedence' , (WidgetTester tester) async { |
153 | const double elevation = 10.0; |
154 | const double focusElevation = 11.0; |
155 | const double hoverElevation = 12.0; |
156 | const double highlightElevation = 13.0; |
157 | const Color focusColor = Color(0xff001122); |
158 | const Color hoverColor = Color(0xff112233); |
159 | const Color highlightColor = Color(0xff223344); |
160 | |
161 | final Finder rawButtonMaterial = find.descendant( |
162 | of: find.byType(MaterialButton), |
163 | matching: find.byType(Material), |
164 | ); |
165 | |
166 | final FocusNode focusNode = FocusNode(debugLabel: 'MaterialButton Node' ); |
167 | await tester.pumpWidget( |
168 | Directionality( |
169 | textDirection: TextDirection.ltr, |
170 | child: MaterialButton( |
171 | focusColor: focusColor, |
172 | hoverColor: hoverColor, |
173 | highlightColor: highlightColor, |
174 | elevation: elevation, |
175 | focusElevation: focusElevation, |
176 | hoverElevation: hoverElevation, |
177 | highlightElevation: highlightElevation, |
178 | focusNode: focusNode, |
179 | onPressed: () { }, |
180 | child: const Text('button' ), |
181 | ), |
182 | ), |
183 | ); |
184 | await tester.pumpAndSettle(); |
185 | FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; |
186 | |
187 | // Base elevation |
188 | Material material = tester.widget<Material>(rawButtonMaterial); |
189 | expect(material.elevation, equals(elevation)); |
190 | |
191 | // Focus elevation overrides base |
192 | focusNode.requestFocus(); |
193 | await tester.pumpAndSettle(); |
194 | material = tester.widget<Material>(rawButtonMaterial); |
195 | RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures' ); |
196 | expect(inkFeatures, paints..rect(color: focusColor)); |
197 | expect(focusNode.hasPrimaryFocus, isTrue); |
198 | expect(material.elevation, equals(focusElevation)); |
199 | |
200 | // Hover elevation overrides focus |
201 | TestGesture? gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); |
202 | await gesture.addPointer(); |
203 | addTearDown(() => gesture?.removePointer()); |
204 | await gesture.moveTo(tester.getCenter(find.byType(MaterialButton))); |
205 | await tester.pumpAndSettle(); |
206 | material = tester.widget<Material>(rawButtonMaterial); |
207 | inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures' ); |
208 | expect(inkFeatures, paints..rect(color: focusColor)..rect(color: hoverColor)); |
209 | expect(material.elevation, equals(hoverElevation)); |
210 | await gesture.removePointer(); |
211 | gesture = null; |
212 | |
213 | // Highlight elevation overrides hover |
214 | final TestGesture gesture2 = await tester.startGesture(tester.getCenter(find.byType(MaterialButton))); |
215 | addTearDown(gesture2.removePointer); |
216 | await tester.pumpAndSettle(); |
217 | material = tester.widget<Material>(rawButtonMaterial); |
218 | inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures' ); |
219 | expect(inkFeatures, paints..rect(color: focusColor)..rect(color: highlightColor)); |
220 | expect(material.elevation, equals(highlightElevation)); |
221 | await gesture2.up(); |
222 | |
223 | focusNode.dispose(); |
224 | }); |
225 | |
226 | testWidgets("MaterialButton's disabledColor takes precedence over its default disabled color." , (WidgetTester tester) async { |
227 | // Regression test for https://github.com/flutter/flutter/issues/30012. |
228 | |
229 | final Finder rawButtonMaterial = find.descendant( |
230 | of: find.byType(MaterialButton), |
231 | matching: find.byType(Material), |
232 | ); |
233 | |
234 | await tester.pumpWidget( |
235 | const Directionality( |
236 | textDirection: TextDirection.ltr, |
237 | child: MaterialButton( |
238 | disabledColor: Color(0xff00ff00), |
239 | onPressed: null, |
240 | child: Text('button' ), |
241 | ), |
242 | ), |
243 | ); |
244 | |
245 | final Material material = tester.widget<Material>(rawButtonMaterial); |
246 | expect(material.color, const Color(0xff00ff00)); |
247 | }); |
248 | |
249 | testWidgets('Default MaterialButton meets a11y contrast guidelines' , (WidgetTester tester) async { |
250 | await tester.pumpWidget( |
251 | MaterialApp( |
252 | home: Scaffold( |
253 | body: Center( |
254 | child: MaterialButton( |
255 | child: const Text('MaterialButton' ), |
256 | onPressed: () { }, |
257 | ), |
258 | ), |
259 | ), |
260 | ), |
261 | ); |
262 | |
263 | // Default, not disabled. |
264 | await expectLater(tester, meetsGuideline(textContrastGuideline)); |
265 | |
266 | // Highlighted (pressed). |
267 | final Offset center = tester.getCenter(find.byType(MaterialButton)); |
268 | final TestGesture gesture = await tester.startGesture(center); |
269 | await tester.pump(); // Start the splash and highlight animations. |
270 | await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way. |
271 | await expectLater(tester, meetsGuideline(textContrastGuideline)); |
272 | |
273 | // Finish gesture to release resources. |
274 | await gesture.up(); |
275 | await tester.pumpAndSettle(); |
276 | }, |
277 | skip: isBrowser, // https://github.com/flutter/flutter/issues/44115 |
278 | ); |
279 | |
280 | testWidgets('MaterialButton gets focus when autofocus is set.' , (WidgetTester tester) async { |
281 | final FocusNode focusNode = FocusNode(debugLabel: 'MaterialButton' ); |
282 | await tester.pumpWidget( |
283 | MaterialApp( |
284 | home: Center( |
285 | child: MaterialButton( |
286 | focusNode: focusNode, |
287 | onPressed: () {}, |
288 | child: Container(width: 100, height: 100, color: const Color(0xffff0000)), |
289 | ), |
290 | ), |
291 | ), |
292 | ); |
293 | |
294 | await tester.pump(); |
295 | expect(focusNode.hasPrimaryFocus, isFalse); |
296 | |
297 | await tester.pumpWidget( |
298 | MaterialApp( |
299 | home: Center( |
300 | child: MaterialButton( |
301 | autofocus: true, |
302 | focusNode: focusNode, |
303 | onPressed: () {}, |
304 | child: Container(width: 100, height: 100, color: const Color(0xffff0000)), |
305 | ), |
306 | ), |
307 | ), |
308 | ); |
309 | |
310 | await tester.pump(); |
311 | expect(focusNode.hasPrimaryFocus, isTrue); |
312 | |
313 | focusNode.dispose(); |
314 | }); |
315 | |
316 | testWidgets('MaterialButton onPressed and onLongPress callbacks are correctly called when non-null' , (WidgetTester tester) async { |
317 | |
318 | bool wasPressed; |
319 | Finder materialButton; |
320 | |
321 | Widget buildFrame({ VoidCallback? onPressed, VoidCallback? onLongPress }) { |
322 | return Directionality( |
323 | textDirection: TextDirection.ltr, |
324 | child: MaterialButton( |
325 | onPressed: onPressed, |
326 | onLongPress: onLongPress, |
327 | child: const Text('button' ), |
328 | ), |
329 | ); |
330 | } |
331 | |
332 | // onPressed not null, onLongPress null. |
333 | wasPressed = false; |
334 | await tester.pumpWidget( |
335 | buildFrame(onPressed: () { wasPressed = true; }), |
336 | ); |
337 | materialButton = find.byType(MaterialButton); |
338 | expect(tester.widget<MaterialButton>(materialButton).enabled, true); |
339 | await tester.tap(materialButton); |
340 | expect(wasPressed, true); |
341 | |
342 | // onPressed null, onLongPress not null. |
343 | wasPressed = false; |
344 | await tester.pumpWidget( |
345 | buildFrame(onLongPress: () { wasPressed = true; }), |
346 | ); |
347 | materialButton = find.byType(MaterialButton); |
348 | expect(tester.widget<MaterialButton>(materialButton).enabled, true); |
349 | await tester.longPress(materialButton); |
350 | expect(wasPressed, true); |
351 | |
352 | // onPressed null, onLongPress null. |
353 | await tester.pumpWidget( |
354 | buildFrame(), |
355 | ); |
356 | materialButton = find.byType(MaterialButton); |
357 | expect(tester.widget<MaterialButton>(materialButton).enabled, false); |
358 | }); |
359 | |
360 | testWidgets('MaterialButton onPressed and onLongPress callbacks are distinctly recognized' , (WidgetTester tester) async { |
361 | bool didPressButton = false; |
362 | bool didLongPressButton = false; |
363 | |
364 | await tester.pumpWidget( |
365 | Directionality( |
366 | textDirection: TextDirection.ltr, |
367 | child: MaterialButton( |
368 | onPressed: () { |
369 | didPressButton = true; |
370 | }, |
371 | onLongPress: () { |
372 | didLongPressButton = true; |
373 | }, |
374 | child: const Text('button' ), |
375 | ), |
376 | ), |
377 | ); |
378 | |
379 | final Finder materialButton = find.byType(MaterialButton); |
380 | expect(tester.widget<MaterialButton>(materialButton).enabled, true); |
381 | |
382 | expect(didPressButton, isFalse); |
383 | await tester.tap(materialButton); |
384 | expect(didPressButton, isTrue); |
385 | |
386 | expect(didLongPressButton, isFalse); |
387 | await tester.longPress(materialButton); |
388 | expect(didLongPressButton, isTrue); |
389 | }); |
390 | |
391 | testWidgets('MaterialButton changes mouse cursor when hovered' , (WidgetTester tester) async { |
392 | await tester.pumpWidget( |
393 | Directionality( |
394 | textDirection: TextDirection.ltr, |
395 | child: MouseRegion( |
396 | cursor: SystemMouseCursors.forbidden, |
397 | child: MaterialButton( |
398 | onPressed: () {}, |
399 | mouseCursor: SystemMouseCursors.text, |
400 | ), |
401 | ), |
402 | ), |
403 | ); |
404 | |
405 | final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1); |
406 | await gesture.addPointer(location: Offset.zero); |
407 | |
408 | await tester.pump(); |
409 | |
410 | expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text); |
411 | |
412 | // Test default cursor |
413 | await tester.pumpWidget( |
414 | Directionality( |
415 | textDirection: TextDirection.ltr, |
416 | child: MouseRegion( |
417 | cursor: SystemMouseCursors.forbidden, |
418 | child: MaterialButton( |
419 | onPressed: () {}, |
420 | ), |
421 | ), |
422 | ), |
423 | ); |
424 | |
425 | expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click); |
426 | |
427 | // Test default cursor when disabled |
428 | await tester.pumpWidget( |
429 | const Directionality( |
430 | textDirection: TextDirection.ltr, |
431 | child: MouseRegion( |
432 | cursor: SystemMouseCursors.forbidden, |
433 | child: MaterialButton( |
434 | onPressed: null, |
435 | ), |
436 | ), |
437 | ), |
438 | ); |
439 | |
440 | expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic); |
441 | }); |
442 | |
443 | // This test is very similar to the '...explicit splashColor and highlightColor' test |
444 | // in icon_button_test.dart. If you change this one, you may want to also change that one. |
445 | testWidgets('MaterialButton with explicit splashColor and highlightColor' , (WidgetTester tester) async { |
446 | const Color directSplashColor = Color(0xFF000011); |
447 | const Color directHighlightColor = Color(0xFF000011); |
448 | |
449 | Widget buttonWidget = Center( |
450 | child: MaterialButton( |
451 | splashColor: directSplashColor, |
452 | highlightColor: directHighlightColor, |
453 | onPressed: () { /* to make sure the button is enabled */ }, |
454 | clipBehavior: Clip.antiAlias, |
455 | ), |
456 | ); |
457 | |
458 | await tester.pumpWidget( |
459 | Directionality( |
460 | textDirection: TextDirection.ltr, |
461 | child: Theme( |
462 | data: ThemeData( |
463 | useMaterial3: false, |
464 | materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, |
465 | ), |
466 | child: buttonWidget, |
467 | ), |
468 | ), |
469 | ); |
470 | |
471 | final Offset center = tester.getCenter(find.byType(MaterialButton)); |
472 | final TestGesture gesture = await tester.startGesture(center); |
473 | await tester.pump(); // start gesture |
474 | await tester.pump(const Duration(milliseconds: 200)); // wait for splash to be well under way |
475 | |
476 | // Painter is translated to the center by the Center widget and not |
477 | // the Material widget. |
478 | const Rect expectedClipRect = Rect.fromLTRB(0.0, 0.0, 88.0, 36.0); |
479 | final Path expectedClipPath = Path() |
480 | ..addRRect(RRect.fromRectAndRadius( |
481 | expectedClipRect, |
482 | const Radius.circular(2.0), |
483 | )); |
484 | expect( |
485 | Material.of(tester.element(find.byType(InkWell))), |
486 | paints |
487 | ..clipPath(pathMatcher: coversSameAreaAs( |
488 | expectedClipPath, |
489 | areaToCompare: expectedClipRect.inflate(10.0), |
490 | )) |
491 | ..circle(color: directSplashColor) |
492 | ..rect(color: directHighlightColor), |
493 | ); |
494 | |
495 | const Color themeSplashColor1 = Color(0xFF001100); |
496 | const Color themeHighlightColor1 = Color(0xFF001100); |
497 | |
498 | buttonWidget = Center( |
499 | child: MaterialButton( |
500 | onPressed: () { /* to make sure the button is enabled */ }, |
501 | clipBehavior: Clip.antiAlias, |
502 | ), |
503 | ); |
504 | |
505 | await tester.pumpWidget( |
506 | Directionality( |
507 | textDirection: TextDirection.ltr, |
508 | child: Theme( |
509 | data: ThemeData( |
510 | useMaterial3: false, |
511 | highlightColor: themeHighlightColor1, |
512 | splashColor: themeSplashColor1, |
513 | materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, |
514 | ), |
515 | child: buttonWidget, |
516 | ), |
517 | ), |
518 | ); |
519 | |
520 | expect( |
521 | Material.of(tester.element(find.byType(InkWell))), |
522 | paints |
523 | ..clipPath(pathMatcher: coversSameAreaAs( |
524 | expectedClipPath, |
525 | areaToCompare: expectedClipRect.inflate(10.0), |
526 | )) |
527 | ..circle(color: themeSplashColor1) |
528 | ..rect(color: themeHighlightColor1), |
529 | ); |
530 | |
531 | const Color themeSplashColor2 = Color(0xFF002200); |
532 | const Color themeHighlightColor2 = Color(0xFF002200); |
533 | |
534 | await tester.pumpWidget( |
535 | Directionality( |
536 | textDirection: TextDirection.ltr, |
537 | child: Theme( |
538 | data: ThemeData( |
539 | useMaterial3: false, |
540 | highlightColor: themeHighlightColor2, |
541 | splashColor: themeSplashColor2, |
542 | materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, |
543 | ), |
544 | child: buttonWidget, // same widget, so does not get updated because of us |
545 | ), |
546 | ), |
547 | ); |
548 | |
549 | expect( |
550 | Material.of(tester.element(find.byType(InkWell))), |
551 | paints |
552 | ..circle(color: themeSplashColor2) |
553 | ..rect(color: themeHighlightColor2), |
554 | ); |
555 | |
556 | await gesture.up(); |
557 | }); |
558 | |
559 | testWidgets('MaterialButton has no clip by default' , (WidgetTester tester) async { |
560 | final GlobalKey buttonKey = GlobalKey(); |
561 | final Widget buttonWidget = Center( |
562 | child: MaterialButton( |
563 | key: buttonKey, |
564 | onPressed: () { /* to make sure the button is enabled */ }, |
565 | ), |
566 | ); |
567 | |
568 | await tester.pumpWidget( |
569 | Directionality( |
570 | textDirection: TextDirection.ltr, |
571 | child: Theme( |
572 | data: ThemeData( |
573 | materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, |
574 | ), |
575 | child: buttonWidget, |
576 | ), |
577 | ), |
578 | ); |
579 | |
580 | expect( |
581 | tester.renderObject(find.byKey(buttonKey)), |
582 | paintsExactlyCountTimes(#clipPath, 0), |
583 | ); |
584 | }); |
585 | |
586 | testWidgets('Disabled MaterialButton has same semantic size as enabled and exposes disabled semantics' , (WidgetTester tester) async { |
587 | final SemanticsTester semantics = SemanticsTester(tester); |
588 | |
589 | const Rect expectedButtonSize = Rect.fromLTRB(0.0, 0.0, 116.0, 48.0); |
590 | // Button is in center of screen |
591 | final Matrix4 expectedButtonTransform = Matrix4.identity() |
592 | ..translate( |
593 | TestSemantics.fullScreen.width / 2 - expectedButtonSize.width /2, |
594 | TestSemantics.fullScreen.height / 2 - expectedButtonSize.height /2, |
595 | ); |
596 | |
597 | // enabled button |
598 | await tester.pumpWidget( |
599 | Theme( |
600 | data: ThemeData(useMaterial3: false), |
601 | child: Directionality( |
602 | textDirection: TextDirection.ltr, |
603 | child: Center( |
604 | child: MaterialButton( |
605 | child: const Text('Button' ), |
606 | onPressed: () { /* to make sure the button is enabled */ }, |
607 | ), |
608 | ), |
609 | ), |
610 | ), |
611 | ); |
612 | |
613 | expect(semantics, hasSemantics( |
614 | TestSemantics.root( |
615 | children: <TestSemantics>[ |
616 | TestSemantics.rootChild( |
617 | id: 1, |
618 | rect: expectedButtonSize, |
619 | transform: expectedButtonTransform, |
620 | label: 'Button' , |
621 | actions: <SemanticsAction>[ |
622 | SemanticsAction.tap, |
623 | SemanticsAction.focus, |
624 | ], |
625 | flags: <SemanticsFlag>[ |
626 | SemanticsFlag.hasEnabledState, |
627 | SemanticsFlag.isButton, |
628 | SemanticsFlag.isEnabled, |
629 | SemanticsFlag.isFocusable, |
630 | ], |
631 | ), |
632 | ], |
633 | ), |
634 | )); |
635 | |
636 | // disabled button |
637 | await tester.pumpWidget( |
638 | Theme( |
639 | data: ThemeData(useMaterial3: false), |
640 | child: const Directionality( |
641 | textDirection: TextDirection.ltr, |
642 | child: Center( |
643 | child: MaterialButton( |
644 | onPressed: null, // button is disabled |
645 | child: Text('Button' ), |
646 | ), |
647 | ), |
648 | ), |
649 | ), |
650 | ); |
651 | |
652 | expect(semantics, hasSemantics( |
653 | TestSemantics.root( |
654 | children: <TestSemantics>[ |
655 | TestSemantics.rootChild( |
656 | id: 1, |
657 | rect: expectedButtonSize, |
658 | transform: expectedButtonTransform, |
659 | label: 'Button' , |
660 | flags: <SemanticsFlag>[ |
661 | SemanticsFlag.hasEnabledState, |
662 | SemanticsFlag.isButton, |
663 | SemanticsFlag.isFocusable, |
664 | ], |
665 | actions: <SemanticsAction>[SemanticsAction.focus], |
666 | ), |
667 | ], |
668 | ), |
669 | )); |
670 | |
671 | |
672 | semantics.dispose(); |
673 | }); |
674 | |
675 | testWidgets('MaterialButton minWidth and height parameters' , (WidgetTester tester) async { |
676 | Widget buildFrame({ double? minWidth, double? height, EdgeInsets padding = EdgeInsets.zero, Widget? child }) { |
677 | return Directionality( |
678 | textDirection: TextDirection.ltr, |
679 | child: Center( |
680 | child: MaterialButton( |
681 | padding: padding, |
682 | minWidth: minWidth, |
683 | height: height, |
684 | onPressed: null, |
685 | materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, |
686 | child: child, |
687 | ), |
688 | ), |
689 | ); |
690 | } |
691 | |
692 | await tester.pumpWidget(buildFrame(minWidth: 8.0, height: 24.0)); |
693 | expect(tester.getSize(find.byType(MaterialButton)), const Size(8.0, 24.0)); |
694 | |
695 | await tester.pumpWidget(buildFrame(minWidth: 8.0)); |
696 | // Default minHeight constraint is 36, see RawMaterialButton. |
697 | expect(tester.getSize(find.byType(MaterialButton)), const Size(8.0, 36.0)); |
698 | |
699 | await tester.pumpWidget(buildFrame(height: 8.0)); |
700 | // Default minWidth constraint is 88, see RawMaterialButton. |
701 | expect(tester.getSize(find.byType(MaterialButton)), const Size(88.0, 8.0)); |
702 | |
703 | await tester.pumpWidget(buildFrame()); |
704 | expect(tester.getSize(find.byType(MaterialButton)), const Size(88.0, 36.0)); |
705 | |
706 | await tester.pumpWidget(buildFrame(padding: const EdgeInsets.all(4.0))); |
707 | expect(tester.getSize(find.byType(MaterialButton)), const Size(88.0, 36.0)); |
708 | |
709 | // Size is defined by the padding. |
710 | await tester.pumpWidget( |
711 | buildFrame( |
712 | minWidth: 0.0, |
713 | height: 0.0, |
714 | padding: const EdgeInsets.all(4.0), |
715 | ), |
716 | ); |
717 | expect(tester.getSize(find.byType(MaterialButton)), const Size(8.0, 8.0)); |
718 | |
719 | // Size is defined by the padded child. |
720 | await tester.pumpWidget( |
721 | buildFrame( |
722 | minWidth: 0.0, |
723 | height: 0.0, |
724 | padding: const EdgeInsets.all(4.0), |
725 | child: const SizedBox(width: 8.0, height: 8.0), |
726 | ), |
727 | ); |
728 | expect(tester.getSize(find.byType(MaterialButton)), const Size(16.0, 16.0)); |
729 | |
730 | // Size is defined by the minWidth, height constraints. |
731 | await tester.pumpWidget( |
732 | buildFrame( |
733 | minWidth: 18.0, |
734 | height: 18.0, |
735 | padding: const EdgeInsets.all(4.0), |
736 | child: const SizedBox(width: 8.0, height: 8.0), |
737 | ), |
738 | ); |
739 | expect(tester.getSize(find.byType(MaterialButton)), const Size(18.0, 18.0)); |
740 | }); |
741 | |
742 | testWidgets('MaterialButton size is configurable by ThemeData.materialTapTargetSize' , (WidgetTester tester) async { |
743 | final Key key1 = UniqueKey(); |
744 | await tester.pumpWidget( |
745 | Theme( |
746 | data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded), |
747 | child: Directionality( |
748 | textDirection: TextDirection.ltr, |
749 | child: Center( |
750 | child: MaterialButton( |
751 | key: key1, |
752 | child: const SizedBox(width: 50.0, height: 8.0), |
753 | onPressed: () { }, |
754 | ), |
755 | ), |
756 | ), |
757 | ), |
758 | ); |
759 | |
760 | expect(tester.getSize(find.byKey(key1)), const Size(88.0, 48.0)); |
761 | |
762 | final Key key2 = UniqueKey(); |
763 | await tester.pumpWidget( |
764 | Theme( |
765 | data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap), |
766 | child: Directionality( |
767 | textDirection: TextDirection.ltr, |
768 | child: Center( |
769 | child: MaterialButton( |
770 | key: key2, |
771 | child: const SizedBox(width: 50.0, height: 8.0), |
772 | onPressed: () { }, |
773 | ), |
774 | ), |
775 | ), |
776 | ), |
777 | ); |
778 | |
779 | expect(tester.getSize(find.byKey(key2)), const Size(88.0, 36.0)); |
780 | }); |
781 | |
782 | testWidgets('MaterialButton shape overrides ButtonTheme shape' , (WidgetTester tester) async { |
783 | // Regression test for https://github.com/flutter/flutter/issues/29146 |
784 | await tester.pumpWidget( |
785 | Directionality( |
786 | textDirection: TextDirection.ltr, |
787 | child: MaterialButton( |
788 | onPressed: () { }, |
789 | shape: const StadiumBorder(), |
790 | child: const Text('button' ), |
791 | ), |
792 | ), |
793 | ); |
794 | |
795 | final Finder rawButtonMaterial = find.descendant( |
796 | of: find.byType(MaterialButton), |
797 | matching: find.byType(Material), |
798 | ); |
799 | expect(tester.widget<Material>(rawButtonMaterial).shape, const StadiumBorder()); |
800 | }); |
801 | |
802 | testWidgets('MaterialButton responds to density changes.' , (WidgetTester tester) async { |
803 | const Key key = Key('test' ); |
804 | const Key childKey = Key('test child' ); |
805 | |
806 | Future<void> buildTest(VisualDensity visualDensity, {bool useText = false}) async { |
807 | return tester.pumpWidget( |
808 | MaterialApp( |
809 | theme: ThemeData(useMaterial3: false), |
810 | home: Directionality( |
811 | textDirection: TextDirection.rtl, |
812 | child: Center( |
813 | child: MaterialButton( |
814 | visualDensity: visualDensity, |
815 | key: key, |
816 | onPressed: () {}, |
817 | child: useText ? const Text('Text' , key: childKey) : Container(key: childKey, width: 100, height: 100, color: const Color(0xffff0000)), |
818 | ), |
819 | ), |
820 | ), |
821 | ), |
822 | ); |
823 | } |
824 | |
825 | await buildTest(VisualDensity.standard); |
826 | final RenderBox box = tester.renderObject(find.byKey(key)); |
827 | Rect childRect = tester.getRect(find.byKey(childKey)); |
828 | await tester.pumpAndSettle(); |
829 | expect(box.size, equals(const Size(132, 100))); |
830 | expect(childRect, equals(const Rect.fromLTRB(350, 250, 450, 350))); |
831 | |
832 | await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0)); |
833 | await tester.pumpAndSettle(); |
834 | childRect = tester.getRect(find.byKey(childKey)); |
835 | expect(box.size, equals(const Size(156, 124))); |
836 | expect(childRect, equals(const Rect.fromLTRB(350, 250, 450, 350))); |
837 | |
838 | await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0)); |
839 | await tester.pumpAndSettle(); |
840 | childRect = tester.getRect(find.byKey(childKey)); |
841 | expect(box.size, equals(const Size(108, 100))); |
842 | expect(childRect, equals(const Rect.fromLTRB(350, 250, 450, 350))); |
843 | |
844 | await buildTest(VisualDensity.standard, useText: true); |
845 | await tester.pumpAndSettle(); |
846 | childRect = tester.getRect(find.byKey(childKey)); |
847 | expect(box.size, equals(const Size(88, 48))); |
848 | expect(childRect, equals(const Rect.fromLTRB(372.0, 293.0, 428.0, 307.0))); |
849 | |
850 | await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0), useText: true); |
851 | await tester.pumpAndSettle(); |
852 | childRect = tester.getRect(find.byKey(childKey)); |
853 | expect(box.size, equals(const Size(112, 60))); |
854 | expect(childRect, equals(const Rect.fromLTRB(372.0, 293.0, 428.0, 307.0))); |
855 | |
856 | await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0), useText: true); |
857 | await tester.pumpAndSettle(); |
858 | childRect = tester.getRect(find.byKey(childKey)); |
859 | expect(box.size, equals(const Size(76, 36))); |
860 | expect(childRect, equals(const Rect.fromLTRB(372.0, 293.0, 428.0, 307.0))); |
861 | }); |
862 | |
863 | testWidgets('disabledElevation is passed to RawMaterialButton' , (WidgetTester tester) async { |
864 | const double disabledElevation = 16; |
865 | |
866 | final Finder rawMaterialButtonFinder = find.descendant( |
867 | of: find.byType(MaterialButton), |
868 | matching: find.byType(RawMaterialButton), |
869 | ); |
870 | |
871 | await tester.pumpWidget( |
872 | const Directionality( |
873 | textDirection: TextDirection.ltr, |
874 | child: MaterialButton( |
875 | disabledElevation: disabledElevation, |
876 | onPressed: null, // disabled button |
877 | child: Text('button' ), |
878 | ), |
879 | ), |
880 | ); |
881 | |
882 | final RawMaterialButton rawMaterialButton = tester.widget(rawMaterialButtonFinder); |
883 | expect(rawMaterialButton.disabledElevation, equals(disabledElevation)); |
884 | }); |
885 | |
886 | testWidgets('MaterialButton.disabledElevation defaults to 0.0 when not provided' , (WidgetTester tester) async { |
887 | final Finder rawMaterialButtonFinder = find.descendant( |
888 | of: find.byType(MaterialButton), |
889 | matching: find.byType(RawMaterialButton), |
890 | ); |
891 | |
892 | await tester.pumpWidget( |
893 | const Directionality( |
894 | textDirection: TextDirection.ltr, |
895 | child: MaterialButton( |
896 | onPressed: null, // disabled button |
897 | child: Text('button' ), |
898 | ), |
899 | ), |
900 | ); |
901 | |
902 | final RawMaterialButton rawMaterialButton = tester.widget(rawMaterialButtonFinder); |
903 | expect(rawMaterialButton.disabledElevation, equals(0.0)); |
904 | }); |
905 | } |
906 | |