slider_theme_test.dart 26.9 KB
Newer Older
1 2 3 4
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:ui' show window;

7
import 'package:flutter/material.dart';
8 9 10 11 12 13 14 15
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/painting.dart';

import '../rendering/mock_canvas.dart';

void main() {
  testWidgets('Slider uses ThemeData slider theme if present', (WidgetTester tester) async {
16
    final ThemeData theme = ThemeData(
17 18 19 20 21
      platform: TargetPlatform.android,
      primarySwatch: Colors.red,
    );
    final SliderThemeData sliderTheme = theme.sliderTheme;

22
    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, enabled: false));
23 24
    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));

25 26 27 28 29 30
    expect(
      sliderBox,
      paints
        ..rect(color: sliderTheme.disabledActiveTrackColor)
        ..rect(color: sliderTheme.disabledInactiveTrackColor),
    );
31 32
  });

33
  testWidgets('Slider overrides ThemeData theme if SliderTheme present', (WidgetTester tester) async {
34
    final ThemeData theme = ThemeData(
35 36 37 38 39
      platform: TargetPlatform.android,
      primarySwatch: Colors.red,
    );
    final SliderThemeData sliderTheme = theme.sliderTheme;
    final SliderThemeData customTheme = sliderTheme.copyWith(
40 41
      activeTrackColor: Colors.purple,
      inactiveTrackColor: Colors.purple.withAlpha(0x3d),
42 43
    );

44
    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, enabled: false));
45 46
    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));

47 48 49 50 51 52
    expect(
      sliderBox,
      paints
        ..rect(color: customTheme.disabledActiveTrackColor)
        ..rect(color: customTheme.disabledInactiveTrackColor),
    );
53 54
  });

55
  testWidgets('SliderThemeData generates correct opacities for fromPrimaryColors', (WidgetTester tester) async {
56 57 58 59
    const Color customColor1 = Color(0xcafefeed);
    const Color customColor2 = Color(0xdeadbeef);
    const Color customColor3 = Color(0xdecaface);
    const Color customColor4 = Color(0xfeedcafe);
60

61
    final SliderThemeData sliderTheme = SliderThemeData.fromPrimaryColors(
62 63 64
      primaryColor: customColor1,
      primaryColorDark: customColor2,
      primaryColorLight: customColor3,
65
      valueIndicatorTextStyle: ThemeData.fallback().accentTextTheme.body2.copyWith(color: customColor4),
66 67
    );

68 69 70 71
    expect(sliderTheme.activeTrackColor, equals(customColor1.withAlpha(0xff)));
    expect(sliderTheme.inactiveTrackColor, equals(customColor1.withAlpha(0x3d)));
    expect(sliderTheme.disabledActiveTrackColor, equals(customColor2.withAlpha(0x52)));
    expect(sliderTheme.disabledInactiveTrackColor, equals(customColor2.withAlpha(0x1f)));
72 73 74 75 76 77
    expect(sliderTheme.activeTickMarkColor, equals(customColor3.withAlpha(0x8a)));
    expect(sliderTheme.inactiveTickMarkColor, equals(customColor1.withAlpha(0x8a)));
    expect(sliderTheme.disabledActiveTickMarkColor, equals(customColor3.withAlpha(0x1f)));
    expect(sliderTheme.disabledInactiveTickMarkColor, equals(customColor2.withAlpha(0x1f)));
    expect(sliderTheme.thumbColor, equals(customColor1.withAlpha(0xff)));
    expect(sliderTheme.disabledThumbColor, equals(customColor2.withAlpha(0x52)));
78
    expect(sliderTheme.overlayColor, equals(customColor1.withAlpha(0x1f)));
79
    expect(sliderTheme.valueIndicatorColor, equals(customColor1.withAlpha(0xff)));
80
    expect(sliderTheme.valueIndicatorTextStyle.color, equals(customColor4));
81 82 83
  });

  testWidgets('SliderThemeData lerps correctly', (WidgetTester tester) async {
84
    final SliderThemeData sliderThemeBlack = SliderThemeData.fromPrimaryColors(
85 86 87
      primaryColor: Colors.black,
      primaryColorDark: Colors.black,
      primaryColorLight: Colors.black,
88
      valueIndicatorTextStyle: ThemeData.fallback().accentTextTheme.body2.copyWith(color: Colors.black),
89
    ).copyWith(trackHeight: 2.0);
90
    final SliderThemeData sliderThemeWhite = SliderThemeData.fromPrimaryColors(
91 92 93
      primaryColor: Colors.white,
      primaryColorDark: Colors.white,
      primaryColorLight: Colors.white,
94
      valueIndicatorTextStyle: ThemeData.fallback().accentTextTheme.body2.copyWith(color: Colors.white),
95
    ).copyWith(trackHeight: 6.0);
96
    final SliderThemeData lerp = SliderThemeData.lerp(sliderThemeBlack, sliderThemeWhite, 0.5);
97
    const Color middleGrey = Color(0xff7f7f7f);
98 99

    expect(lerp.trackHeight, equals(4.0));
100 101 102 103
    expect(lerp.activeTrackColor, equals(middleGrey.withAlpha(0xff)));
    expect(lerp.inactiveTrackColor, equals(middleGrey.withAlpha(0x3d)));
    expect(lerp.disabledActiveTrackColor, equals(middleGrey.withAlpha(0x52)));
    expect(lerp.disabledInactiveTrackColor, equals(middleGrey.withAlpha(0x1f)));
104 105 106 107 108 109
    expect(lerp.activeTickMarkColor, equals(middleGrey.withAlpha(0x8a)));
    expect(lerp.inactiveTickMarkColor, equals(middleGrey.withAlpha(0x8a)));
    expect(lerp.disabledActiveTickMarkColor, equals(middleGrey.withAlpha(0x1f)));
    expect(lerp.disabledInactiveTickMarkColor, equals(middleGrey.withAlpha(0x1f)));
    expect(lerp.thumbColor, equals(middleGrey.withAlpha(0xff)));
    expect(lerp.disabledThumbColor, equals(middleGrey.withAlpha(0x52)));
110
    expect(lerp.overlayColor, equals(middleGrey.withAlpha(0x1f)));
111
    expect(lerp.valueIndicatorColor, equals(middleGrey.withAlpha(0xff)));
112
    expect(lerp.valueIndicatorTextStyle.color, equals(middleGrey.withAlpha(0xff)));
113 114
  });

115 116 117 118 119 120 121
  testWidgets('Default slider track draws correctly', (WidgetTester tester) async {
    final ThemeData theme = ThemeData(
      platform: TargetPlatform.android,
      primarySwatch: Colors.blue,
    );
    final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);

122
    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
123 124
    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));

125 126
    // The enabled slider thumb has track segments that extend to and from
    // the center of the thumb.
127 128 129
    expect(
      sliderBox,
      paints
Dan Field's avatar
Dan Field committed
130 131
        ..rect(rect: const Rect.fromLTRB(25.0, 299.0, 202.0, 301.0), color: sliderTheme.activeTrackColor)
        ..rect(rect: const Rect.fromLTRB(222.0, 299.0, 776.0, 301.0), color: sliderTheme.inactiveTrackColor),
132 133
    );

134
    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false));
135
    await tester.pumpAndSettle(); // wait for disable animation
136 137 138 139 140 141 142

    // The disabled slider thumb has a horizontal gap between itself and the
    // track segments. Therefore, the track segments are shorter since they do
    // not extend to the center of the thumb, but rather the outer edge of th
    // gap. As a result, the `right` value of the first segment is less than it
    // is above, and the `left` value of the second segment is more than it is
    // above.
143 144 145
    expect(
      sliderBox,
      paints
Dan Field's avatar
Dan Field committed
146 147
        ..rect(rect: const Rect.fromLTRB(25.0, 299.0, 202.0, 301.0), color: sliderTheme.disabledActiveTrackColor)
        ..rect(rect: const Rect.fromLTRB(222.0, 299.0, 776.0, 301.0), color: sliderTheme.disabledInactiveTrackColor),
148 149 150 151 152 153 154 155 156 157
    );
  });

  testWidgets('Default slider overlay draws correctly', (WidgetTester tester) async {
    final ThemeData theme = ThemeData(
      platform: TargetPlatform.android,
      primarySwatch: Colors.blue,
    );
    final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);

158
    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
159 160 161 162 163 164 165 166
    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));

    // With no touch, paints only the thumb.
    expect(
      sliderBox,
      paints
        ..circle(
          color: sliderTheme.thumbColor,
167
          x: 212.0,
168
          y: 300.0,
169
          radius: 10.0,
170
        ),
171 172 173 174 175 176 177 178 179 180 181 182 183
    );

    final Offset center = tester.getCenter(find.byType(Slider));
    final TestGesture gesture = await tester.startGesture(center);
    // Wait for overlay animation to finish.
    await tester.pumpAndSettle();

    // After touch, paints thumb and overlay.
    expect(
      sliderBox,
      paints
        ..circle(
          color: sliderTheme.overlayColor,
184
          x: 212.0,
185
          y: 300.0,
186
          radius: 24.0,
187 188 189
        )
        ..circle(
          color: sliderTheme.thumbColor,
190
          x: 212.0,
191
          y: 300.0,
192
          radius: 10.0,
193
        ),
194 195 196 197
    );

    await gesture.up();
    await tester.pumpAndSettle();
198

199 200 201 202 203 204
    // After the gesture is up and complete, it again paints only the thumb.
    expect(
      sliderBox,
      paints
        ..circle(
          color: sliderTheme.thumbColor,
205
          x: 212.0,
206
          y: 300.0,
207
          radius: 10.0,
208
        ),
209 210 211 212
    );
  });

  testWidgets('Default slider ticker and thumb shape draw correctly', (WidgetTester tester) async {
213
    final ThemeData theme = ThemeData(
214 215 216 217 218
      platform: TargetPlatform.android,
      primarySwatch: Colors.blue,
    );
    final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);

219
    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45));
220 221
    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));

222
    expect(sliderBox, paints..circle(color: sliderTheme.thumbColor, radius: 10.0));
223

224
    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45, enabled: false));
225
    await tester.pumpAndSettle(); // wait for disable animation
226

227
    expect(sliderBox, paints..circle(color: sliderTheme.disabledThumbColor, radius: 10.0));
228

229 230 231
    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45, divisions: 3));
    await tester.pumpAndSettle(); // wait for enable animation

232
    expect(
233 234 235 236 237 238
      sliderBox,
      paints
        ..circle(color: sliderTheme.activeTickMarkColor)
        ..circle(color: sliderTheme.activeTickMarkColor)
        ..circle(color: sliderTheme.inactiveTickMarkColor)
        ..circle(color: sliderTheme.inactiveTickMarkColor)
239
        ..circle(color: sliderTheme.thumbColor, radius: 10.0),
240
    );
241

242
    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45, divisions: 3, enabled: false));
243
    await tester.pumpAndSettle(); // wait for disable animation
244

245
    expect(
246 247 248 249 250 251
      sliderBox,
      paints
        ..circle(color: sliderTheme.disabledActiveTickMarkColor)
        ..circle(color: sliderTheme.disabledInactiveTickMarkColor)
        ..circle(color: sliderTheme.disabledInactiveTickMarkColor)
        ..circle(color: sliderTheme.disabledInactiveTickMarkColor)
252
        ..circle(color: sliderTheme.disabledThumbColor, radius: 10.0),
253
    );
254 255 256
  });

  testWidgets('Default slider value indicator shape draws correctly', (WidgetTester tester) async {
257
    final ThemeData theme = ThemeData(
258 259 260
      platform: TargetPlatform.android,
      primarySwatch: Colors.blue,
    );
261
    final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500, showValueIndicator: ShowValueIndicator.always);
262
    Widget buildApp(String value, { double sliderValue = 0.5, double textScale = 1.0 }) {
263
      return Directionality(
264
        textDirection: TextDirection.ltr,
265 266 267 268
        child: MediaQuery(
          data: MediaQueryData.fromWindow(window).copyWith(textScaleFactor: textScale),
          child: Material(
            child: Row(
269
              children: <Widget>[
270 271
                Expanded(
                  child: SliderTheme(
272
                    data: sliderTheme,
273
                    child: Slider(
274 275 276
                      value: sliderValue,
                      label: '$value',
                      divisions: 3,
277
                      onChanged: (double d) { },
278 279 280 281
                    ),
                  ),
                ),
              ],
282 283 284 285 286 287 288 289 290 291 292 293 294
            ),
          ),
        ),
      );
    }

    await tester.pumpWidget(buildApp('1'));

    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));

    Offset center = tester.getCenter(find.byType(Slider));
    TestGesture gesture = await tester.startGesture(center);
    // Wait for value indicator animation to finish.
295
    await tester.pumpAndSettle();
296
    expect(
297 298 299 300 301 302 303 304 305 306
      sliderBox,
      paints
        ..path(
          color: sliderTheme.valueIndicatorColor,
          includes: <Offset>[
            const Offset(0.0, -40.0),
            const Offset(15.9, -40.0),
            const Offset(-15.9, -40.0),
          ],
          excludes: <Offset>[const Offset(16.1, -40.0), const Offset(-16.1, -40.0)],
307
        ),
308
    );
309 310 311 312 313 314 315 316

    await gesture.up();

    // Test that it expands with a larger label.
    await tester.pumpWidget(buildApp('1000'));
    center = tester.getCenter(find.byType(Slider));
    gesture = await tester.startGesture(center);
    // Wait for value indicator animation to finish.
317
    await tester.pumpAndSettle();
318
    expect(
319 320 321 322 323 324 325 326 327 328
      sliderBox,
      paints
        ..path(
          color: sliderTheme.valueIndicatorColor,
          includes: <Offset>[
            const Offset(0.0, -40.0),
            const Offset(35.9, -40.0),
            const Offset(-35.9, -40.0),
          ],
          excludes: <Offset>[const Offset(36.1, -40.0), const Offset(-36.1, -40.0)],
329
        ),
330
    );
331
    await gesture.up();
332 333 334 335 336 337

    // Test that it avoids the left edge of the screen.
    await tester.pumpWidget(buildApp('1000000', sliderValue: 0.0));
    center = tester.getCenter(find.byType(Slider));
    gesture = await tester.startGesture(center);
    // Wait for value indicator animation to finish.
338
    await tester.pumpAndSettle();
339
    expect(
340 341 342 343 344 345
      sliderBox,
      paints
        ..path(
          color: sliderTheme.valueIndicatorColor,
          includes: <Offset>[
            const Offset(0.0, -40.0),
346
            const Offset(92.0, -40.0),
347 348
            const Offset(-16.0, -40.0),
          ],
349
          excludes: <Offset>[const Offset(98.1, -40.0), const Offset(-20.1, -40.0)],
350
        ),
351
    );
352 353 354 355 356 357 358
    await gesture.up();

    // Test that it avoids the right edge of the screen.
    await tester.pumpWidget(buildApp('1000000', sliderValue: 1.0));
    center = tester.getCenter(find.byType(Slider));
    gesture = await tester.startGesture(center);
    // Wait for value indicator animation to finish.
359
    await tester.pumpAndSettle();
360
    expect(
361 362 363 364 365 366 367
      sliderBox,
      paints
        ..path(
          color: sliderTheme.valueIndicatorColor,
          includes: <Offset>[
            const Offset(0.0, -40.0),
            const Offset(16.0, -40.0),
368
            const Offset(-92.0, -40.0),
369
          ],
370
          excludes: <Offset>[const Offset(20.1, -40.0), const Offset(-98.1, -40.0)],
371
        ),
372
    );
373
    await gesture.up();
374 375 376 377 378 379 380 381

    // Test that the neck stretches when the text scale gets smaller.
    await tester.pumpWidget(buildApp('1000000', sliderValue: 0.0, textScale: 0.5));
    center = tester.getCenter(find.byType(Slider));
    gesture = await tester.startGesture(center);
    // Wait for value indicator animation to finish.
    await tester.pumpAndSettle();
    expect(
382 383 384 385 386 387
      sliderBox,
      paints
        ..path(
          color: sliderTheme.valueIndicatorColor,
          includes: <Offset>[
            const Offset(0.0, -49.0),
388
            const Offset(68.0, -49.0),
389 390 391 392
            const Offset(-24.0, -49.0),
          ],
          excludes: <Offset>[
            const Offset(98.0, -32.0),  // inside full size, outside small
393
            const Offset(-40.0, -32.0),  // inside full size, outside small
394
            const Offset(90.1, -49.0),
395
            const Offset(-40.1, -49.0),
396
          ],
397
        ),
398
    );
399 400 401 402 403 404 405 406 407
    await gesture.up();

    // Test that the neck shrinks when the text scale gets larger.
    await tester.pumpWidget(buildApp('1000000', sliderValue: 0.0, textScale: 2.5));
    center = tester.getCenter(find.byType(Slider));
    gesture = await tester.startGesture(center);
    // Wait for value indicator animation to finish.
    await tester.pumpAndSettle();
    expect(
408 409 410 411 412 413
      sliderBox,
      paints
        ..path(
          color: sliderTheme.valueIndicatorColor,
          includes: <Offset>[
            const Offset(0.0, -38.8),
414 415 416
            const Offset(92.0, -38.8),
            const Offset(8.0, -23.0), // Inside large, outside scale=1.0
            const Offset(-2.0, -23.0), // Inside large, outside scale=1.0
417 418 419 420 421
          ],
          excludes: <Offset>[
            const Offset(98.5, -38.8),
            const Offset(-16.1, -38.8),
          ],
422
        ),
423
    );
424
    await gesture.up();
425
  });
426 427 428 429 430 431 432 433 434 435 436 437

  testWidgets('The slider track height can be overridden', (WidgetTester tester) async {
    final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(trackHeight: 16);

    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));

    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));

    // Top and bottom are centerY (300) + and - trackRadius (8).
    expect(
      sliderBox,
      paints
Dan Field's avatar
Dan Field committed
438 439
        ..rect(rect: const Rect.fromLTRB(32.0, 292.0, 202.0, 308.0), color: sliderTheme.activeTrackColor)
        ..rect(rect: const Rect.fromLTRB(222.0, 292.0, 776.0, 308.0), color: sliderTheme.inactiveTrackColor),
440 441 442 443 444 445 446 447 448 449
    );

    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false));
    await tester.pumpAndSettle(); // wait for disable animation

    // The disabled thumb is smaller so the active track has to paint longer to
    // get to the edge.
    expect(
      sliderBox,
      paints
Dan Field's avatar
Dan Field committed
450 451
        ..rect(rect: const Rect.fromLTRB(32.0, 292.0, 202.0, 308.0), color: sliderTheme.disabledActiveTrackColor)
        ..rect(rect: const Rect.fromLTRB(222.0, 292.0, 776.0, 308.0), color: sliderTheme.disabledInactiveTrackColor),
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
    );
  });

  testWidgets('The default slider thumb shape sizes can be overridden', (WidgetTester tester) async {
    final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
        thumbShape: const RoundSliderThumbShape(
          enabledThumbRadius: 7,
          disabledThumbRadius: 11,
        ),
    );

    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));

    expect(
      sliderBox,
468
      paints..circle(x: 212, y: 300, radius: 7, color: sliderTheme.thumbColor),
469 470 471 472 473 474 475
    );

    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false));
    await tester.pumpAndSettle(); // wait for disable animation

    expect(
      sliderBox,
476
      paints..circle(x: 212, y: 300, radius: 11, color: sliderTheme.disabledThumbColor),
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
    );
  });

  testWidgets('The default slider thumb shape disabled size can be inferred from the enabled size', (WidgetTester tester) async {
    final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
      thumbShape: const RoundSliderThumbShape(
        enabledThumbRadius: 9,
      ),
    );

    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));

    expect(
      sliderBox,
492
      paints..circle(x: 212, y: 300, radius: 9, color: sliderTheme.thumbColor),
493 494 495 496 497 498
    );

    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false));
    await tester.pumpAndSettle(); // wait for disable animation
    expect(
      sliderBox,
499
      paints..circle(x: 212, y: 300, radius: 9, color: sliderTheme.disabledThumbColor),
500 501 502 503 504 505
    );
  });


  testWidgets('The default slider tick mark shape size can be overridden', (WidgetTester tester) async {
    final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
506
      tickMarkShape: const RoundSliderTickMarkShape(tickMarkRadius: 5),
507 508 509 510 511 512 513 514 515 516 517 518 519
      activeTickMarkColor: const Color(0xfadedead),
      inactiveTickMarkColor: const Color(0xfadebeef),
      disabledActiveTickMarkColor: const Color(0xfadecafe),
      disabledInactiveTickMarkColor: const Color(0xfadeface),
    );

    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, divisions: 2));

    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));

    expect(
      sliderBox,
      paints
520
        ..circle(x: 29, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor)
521
        ..circle(x: 400, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor)
522
        ..circle(x: 771, y: 300, radius: 5, color: sliderTheme.inactiveTickMarkColor),
523 524 525 526 527 528 529 530
    );

    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, divisions: 2,  enabled: false));
    await tester.pumpAndSettle();

    expect(
      sliderBox,
      paints
531
        ..circle(x: 29, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor)
532
        ..circle(x: 400, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor)
533
        ..circle(x: 771, y: 300, radius: 5, color: sliderTheme.disabledInactiveTickMarkColor),
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558
    );
  });

  testWidgets('The default slider overlay shape size can be overridden', (WidgetTester tester) async {
    const double uniqueOverlayRadius = 23;
    final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
      overlayShape: const RoundSliderOverlayShape(
        overlayRadius: uniqueOverlayRadius,
      ),
    );

    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5));
    // Tap center and wait for animation.
    final Offset center = tester.getCenter(find.byType(Slider));
    await tester.startGesture(center);
    await tester.pumpAndSettle();

    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
    expect(
      sliderBox,
      paints..circle(
        x: center.dx,
        y: center.dy,
        radius: uniqueOverlayRadius,
        color: sliderTheme.overlayColor,
559
      ),
560 561
    );
  });
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580

  // Only the thumb, overlay, and tick mark have special shortcuts to provide
  // no-op or empty shapes.
  //
  // The track can also be skipped by providing 0 height.
  //
  // The value indicator can be skipped by passing the appropriate
  // [ShowValueIndicator].
  testWidgets('The slider can skip all of its comoponent painting', (WidgetTester tester) async {
    // Pump a slider with all shapes skipped.
    await tester.pumpWidget(_buildApp(
      ThemeData().sliderTheme.copyWith(
        trackHeight: 0,
        overlayShape: SliderComponentShape.noOverlay,
        thumbShape: SliderComponentShape.noThumb,
        tickMarkShape: SliderTickMarkShape.noTickMark,
        showValueIndicator: ShowValueIndicator.never,
      ),
      value: 0.5,
581
      divisions: 4,
582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598
    ));

    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));

    expect(sliderBox, paintsNothing);
  });

  testWidgets('The slider can skip all component painting except the track', (WidgetTester tester) async {
    // Pump a slider with just a track.
    await tester.pumpWidget(_buildApp(
      ThemeData().sliderTheme.copyWith(
        overlayShape: SliderComponentShape.noOverlay,
        thumbShape: SliderComponentShape.noThumb,
        tickMarkShape: SliderTickMarkShape.noTickMark,
        showValueIndicator: ShowValueIndicator.never,
      ),
      value: 0.5,
599
      divisions: 4,
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619
    ));

    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));

    // Only 2 track segments.
    expect(sliderBox, paintsExactlyCountTimes(#drawRect, 2));
    expect(sliderBox, paintsExactlyCountTimes(#drawCircle, 0));
    expect(sliderBox, paintsExactlyCountTimes(#drawPath, 0));
  });

  testWidgets('The slider can skip all component painting except the tick marks', (WidgetTester tester) async {
    // Pump a slider with just tick marks.
    await tester.pumpWidget(_buildApp(
      ThemeData().sliderTheme.copyWith(
        trackHeight: 0,
        overlayShape: SliderComponentShape.noOverlay,
        thumbShape: SliderComponentShape.noThumb,
        showValueIndicator: ShowValueIndicator.never,
      ),
      value: 0.5,
620
      divisions: 4,
621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
    ));

    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));

    // Only 5 tick marks.
    expect(sliderBox, paintsExactlyCountTimes(#drawRect, 0));
    expect(sliderBox, paintsExactlyCountTimes(#drawCircle, 5));
    expect(sliderBox, paintsExactlyCountTimes(#drawPath, 0));
  });

  testWidgets('The slider can skip all component painting except the thumb', (WidgetTester tester) async {
    // Pump a slider with just a thumb.
    await tester.pumpWidget(_buildApp(
      ThemeData().sliderTheme.copyWith(
        trackHeight: 0,
        overlayShape: SliderComponentShape.noOverlay,
        tickMarkShape: SliderTickMarkShape.noTickMark,
        showValueIndicator: ShowValueIndicator.never,
      ),
      value: 0.5,
641
      divisions: 4,
642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
    ));

    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));

    // Only 1 thumb.
    expect(sliderBox, paintsExactlyCountTimes(#drawRect, 0));
    expect(sliderBox, paintsExactlyCountTimes(#drawCircle, 1));
    expect(sliderBox, paintsExactlyCountTimes(#drawPath, 0));
  });

  testWidgets('The slider can skip all component painting except the overlay', (WidgetTester tester) async {
    // Pump a slider with just an overlay.
    await tester.pumpWidget(_buildApp(
      ThemeData().sliderTheme.copyWith(
        trackHeight: 0,
        thumbShape: SliderComponentShape.noThumb,
        tickMarkShape: SliderTickMarkShape.noTickMark,
        showValueIndicator: ShowValueIndicator.never,
      ),
      value: 0.5,
662
      divisions: 4,
663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690
    ));

    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));

    // Tap the center of the track and wait for animations to finish.
    final Offset center = tester.getCenter(find.byType(Slider));
    final TestGesture gesture = await tester.startGesture(center);
    await tester.pumpAndSettle();

    // Only 1 overlay.
    expect(sliderBox, paintsExactlyCountTimes(#drawRect, 0));
    expect(sliderBox, paintsExactlyCountTimes(#drawCircle, 1));
    expect(sliderBox, paintsExactlyCountTimes(#drawPath, 0));

    await gesture.up();
  });

  testWidgets('The slider can skip all component painting except the value indicator', (WidgetTester tester) async {
    // Pump a slider with just a value indicator.
    await tester.pumpWidget(_buildApp(
      ThemeData().sliderTheme.copyWith(
        trackHeight: 0,
        overlayShape: SliderComponentShape.noOverlay,
        thumbShape: SliderComponentShape.noThumb,
        tickMarkShape: SliderTickMarkShape.noTickMark,
        showValueIndicator: ShowValueIndicator.always,
      ),
      value: 0.5,
691
      divisions: 4,
692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707
    ));

    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));

    // Tap the center of the track and wait for animations to finish.
    final Offset center = tester.getCenter(find.byType(Slider));
    final TestGesture gesture = await tester.startGesture(center);
    await tester.pumpAndSettle();

    // Only 1 value indicator.
    expect(sliderBox, paintsExactlyCountTimes(#drawRect, 0));
    expect(sliderBox, paintsExactlyCountTimes(#drawCircle, 0));
    expect(sliderBox, paintsExactlyCountTimes(#drawPath, 1));

    await gesture.up();
  });
708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731
}

Widget _buildApp(
  SliderThemeData sliderTheme, {
  double value = 0.0,
  bool enabled = true,
  int divisions,
}) {
  final ValueChanged<double> onChanged = enabled ? (double d) => value = d : null;
  return MaterialApp(
    home: Scaffold(
      body: Center(
        child: SliderTheme(
          data: sliderTheme,
          child: Slider(
            value: value,
            label: '$value',
            onChanged: onChanged,
            divisions: divisions,
          ),
        ),
      ),
    ),
  );
732
}