• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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