slider_theme_test.dart 28.4 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 theme is built by ThemeData', (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 23
    expect(sliderTheme.activeTrackColor.value, equals(Colors.red.value));
    expect(sliderTheme.inactiveTrackColor.value, equals(Colors.red.withAlpha(0x3d).value));
24 25 26
  });

  testWidgets('Slider uses ThemeData slider theme if present', (WidgetTester tester) async {
27
    final ThemeData theme = ThemeData(
28 29 30 31 32
      platform: TargetPlatform.android,
      primarySwatch: Colors.red,
    );
    final SliderThemeData sliderTheme = theme.sliderTheme;

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

36 37 38 39 40 41
    expect(
      sliderBox,
      paints
        ..rect(color: sliderTheme.disabledActiveTrackColor)
        ..rect(color: sliderTheme.disabledInactiveTrackColor),
    );
42 43
  });

44
  testWidgets('Slider overrides ThemeData theme if SliderTheme present', (WidgetTester tester) async {
45
    final ThemeData theme = ThemeData(
46 47 48 49 50
      platform: TargetPlatform.android,
      primarySwatch: Colors.red,
    );
    final SliderThemeData sliderTheme = theme.sliderTheme;
    final SliderThemeData customTheme = sliderTheme.copyWith(
51 52
      activeTrackColor: Colors.purple,
      inactiveTrackColor: Colors.purple.withAlpha(0x3d),
53 54
    );

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

58 59 60 61 62 63
    expect(
      sliderBox,
      paints
        ..rect(color: customTheme.disabledActiveTrackColor)
        ..rect(color: customTheme.disabledInactiveTrackColor),
    );
64 65
  });

66 67 68 69 70 71 72 73 74 75 76 77 78 79
  testWidgets('SliderThemeData assigns the correct default shapes', (WidgetTester tester) async {
    final SliderThemeData sliderTheme = ThemeData().sliderTheme;
    expect(sliderTheme.trackShape, equals(isInstanceOf<RectangularSliderTrackShape>()));
    expect(sliderTheme.tickMarkShape, equals(isInstanceOf<RoundSliderTickMarkShape>()));
    expect(sliderTheme.thumbShape, equals(isInstanceOf<RoundSliderThumbShape>()));
    expect(sliderTheme.valueIndicatorShape, equals(isInstanceOf<PaddleSliderValueIndicatorShape>()));
    expect(sliderTheme.overlayShape, equals(isInstanceOf<RoundSliderOverlayShape>()));
  });

  testWidgets('SliderThemeData assigns the correct default flags', (WidgetTester tester) async {
    final SliderThemeData sliderTheme = ThemeData().sliderTheme;
    expect(sliderTheme.showValueIndicator, equals(ShowValueIndicator.onlyForDiscrete));
  });

80
  testWidgets('SliderThemeData generates correct opacities for fromPrimaryColors', (WidgetTester tester) async {
81 82 83 84
    const Color customColor1 = Color(0xcafefeed);
    const Color customColor2 = Color(0xdeadbeef);
    const Color customColor3 = Color(0xdecaface);
    const Color customColor4 = Color(0xfeedcafe);
85

86
    final SliderThemeData sliderTheme = SliderThemeData.fromPrimaryColors(
87 88 89
      primaryColor: customColor1,
      primaryColorDark: customColor2,
      primaryColorLight: customColor3,
90
      valueIndicatorTextStyle: ThemeData.fallback().accentTextTheme.body2.copyWith(color: customColor4),
91 92
    );

93 94 95 96
    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)));
97 98 99 100 101 102 103 104
    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)));
    expect(sliderTheme.overlayColor, equals(customColor1.withAlpha(0x29)));
    expect(sliderTheme.valueIndicatorColor, equals(customColor1.withAlpha(0xff)));
105
    expect(sliderTheme.valueIndicatorTextStyle.color, equals(customColor4));
106 107 108
  });

  testWidgets('SliderThemeData lerps correctly', (WidgetTester tester) async {
109
    final SliderThemeData sliderThemeBlack = SliderThemeData.fromPrimaryColors(
110 111 112
      primaryColor: Colors.black,
      primaryColorDark: Colors.black,
      primaryColorLight: Colors.black,
113
      valueIndicatorTextStyle: ThemeData.fallback().accentTextTheme.body2.copyWith(color: Colors.black),
114
    ).copyWith(trackHeight: 2.0);
115
    final SliderThemeData sliderThemeWhite = SliderThemeData.fromPrimaryColors(
116 117 118
      primaryColor: Colors.white,
      primaryColorDark: Colors.white,
      primaryColorLight: Colors.white,
119
      valueIndicatorTextStyle: ThemeData.fallback().accentTextTheme.body2.copyWith(color: Colors.white),
120
    ).copyWith(trackHeight: 6.0);
121
    final SliderThemeData lerp = SliderThemeData.lerp(sliderThemeBlack, sliderThemeWhite, 0.5);
122
    const Color middleGrey = Color(0xff7f7f7f);
123 124

    expect(lerp.trackHeight, equals(4.0));
125 126 127 128
    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)));
129 130 131 132 133 134 135 136
    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)));
    expect(lerp.overlayColor, equals(middleGrey.withAlpha(0x29)));
    expect(lerp.valueIndicatorColor, equals(middleGrey.withAlpha(0xff)));
137
    expect(lerp.valueIndicatorTextStyle.color, equals(middleGrey.withAlpha(0xff)));
138 139
  });

140 141 142 143 144 145 146
  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);

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

150 151
    // The enabled slider thumb has track segments that extend to and from
    // the center of the thumb.
152 153 154 155
    expect(
      sliderBox,
      paints
        ..rect(rect: Rect.fromLTRB(16.0, 299.0, 208.0, 301.0), color: sliderTheme.activeTrackColor)
156
        ..rect(rect: Rect.fromLTRB(208.0, 299.0, 784.0, 301.0), color: sliderTheme.inactiveTrackColor),
157 158
    );

159
    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false));
160
    await tester.pumpAndSettle(); // wait for disable animation
161 162 163 164 165 166 167

    // 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.
168 169 170 171
    expect(
      sliderBox,
      paints
        ..rect(rect: Rect.fromLTRB(16.0, 299.0, 202.0, 301.0), color: sliderTheme.disabledActiveTrackColor)
172
        ..rect(rect: Rect.fromLTRB(214.0, 299.0, 784.0, 301.0), color: sliderTheme.disabledInactiveTrackColor),
173 174 175 176 177 178 179 180 181 182
    );
  });

  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);

183
    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
184 185 186 187 188 189 190 191 192 193 194
    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));

    // With no touch, paints only the thumb.
    expect(
      sliderBox,
      paints
        ..circle(
          color: sliderTheme.thumbColor,
          x: 208.0,
          y: 300.0,
          radius: 6.0,
195
        ),
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
    );

    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,
          x: 208.0,
          y: 300.0,
          radius: 16.0,
        )
        ..circle(
          color: sliderTheme.thumbColor,
          x: 208.0,
          y: 300.0,
          radius: 6.0,
218
        ),
219 220 221 222
    );

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

224 225 226 227 228 229 230 231 232
    // After the gesture is up and complete, it again paints only the thumb.
    expect(
      sliderBox,
      paints
        ..circle(
          color: sliderTheme.thumbColor,
          x: 208.0,
          y: 300.0,
          radius: 6.0,
233
        ),
234 235 236 237
    );
  });

  testWidgets('Default slider ticker and thumb shape draw correctly', (WidgetTester tester) async {
238
    final ThemeData theme = ThemeData(
239 240 241 242 243
      platform: TargetPlatform.android,
      primarySwatch: Colors.blue,
    );
    final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);

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

247
    expect(sliderBox, paints..circle(color: sliderTheme.thumbColor, radius: 6.0));
248

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

252 253
    expect(sliderBox, paints..circle(color: sliderTheme.disabledThumbColor, radius: 4.0));

254 255 256
    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45, divisions: 3));
    await tester.pumpAndSettle(); // wait for enable animation

257
    expect(
258 259 260 261 262 263
      sliderBox,
      paints
        ..circle(color: sliderTheme.activeTickMarkColor)
        ..circle(color: sliderTheme.activeTickMarkColor)
        ..circle(color: sliderTheme.inactiveTickMarkColor)
        ..circle(color: sliderTheme.inactiveTickMarkColor)
264
        ..circle(color: sliderTheme.thumbColor, radius: 6.0),
265
    );
266

267
    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45, divisions: 3, enabled: false));
268
    await tester.pumpAndSettle(); // wait for disable animation
269

270
    expect(
271 272 273 274 275 276
      sliderBox,
      paints
        ..circle(color: sliderTheme.disabledActiveTickMarkColor)
        ..circle(color: sliderTheme.disabledInactiveTickMarkColor)
        ..circle(color: sliderTheme.disabledInactiveTickMarkColor)
        ..circle(color: sliderTheme.disabledInactiveTickMarkColor)
277
        ..circle(color: sliderTheme.disabledThumbColor, radius: 4.0),
278
    );
279 280 281
  });

  testWidgets('Default slider value indicator shape draws correctly', (WidgetTester tester) async {
282
    final ThemeData theme = ThemeData(
283 284 285
      platform: TargetPlatform.android,
      primarySwatch: Colors.blue,
    );
286
    final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500, showValueIndicator: ShowValueIndicator.always);
287
    Widget buildApp(String value, { double sliderValue = 0.5, double textScale = 1.0 }) {
288
      return Directionality(
289
        textDirection: TextDirection.ltr,
290 291 292 293
        child: MediaQuery(
          data: MediaQueryData.fromWindow(window).copyWith(textScaleFactor: textScale),
          child: Material(
            child: Row(
294
              children: <Widget>[
295 296
                Expanded(
                  child: SliderTheme(
297
                    data: sliderTheme,
298
                    child: Slider(
299 300 301
                      value: sliderValue,
                      label: '$value',
                      divisions: 3,
302
                      onChanged: (double d) { },
303 304 305 306
                    ),
                  ),
                ),
              ],
307 308 309 310 311 312 313 314 315 316 317 318 319
            ),
          ),
        ),
      );
    }

    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.
320
    await tester.pumpAndSettle();
321
    expect(
322 323 324 325 326 327 328 329 330 331
      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)],
332
        ),
333
    );
334 335 336 337 338 339 340 341

    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.
342
    await tester.pumpAndSettle();
343
    expect(
344 345 346 347 348 349 350 351 352 353
      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)],
354
        ),
355
    );
356
    await gesture.up();
357 358 359 360 361 362

    // 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.
363
    await tester.pumpAndSettle();
364
    expect(
365 366 367 368 369 370 371 372 373 374
      sliderBox,
      paints
        ..path(
          color: sliderTheme.valueIndicatorColor,
          includes: <Offset>[
            const Offset(0.0, -40.0),
            const Offset(98.0, -40.0),
            const Offset(-16.0, -40.0),
          ],
          excludes: <Offset>[const Offset(98.1, -40.0), const Offset(-16.1, -40.0)],
375
        ),
376
    );
377 378 379 380 381 382 383
    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.
384
    await tester.pumpAndSettle();
385
    expect(
386 387 388 389 390 391 392 393 394 395
      sliderBox,
      paints
        ..path(
          color: sliderTheme.valueIndicatorColor,
          includes: <Offset>[
            const Offset(0.0, -40.0),
            const Offset(16.0, -40.0),
            const Offset(-98.0, -40.0),
          ],
          excludes: <Offset>[const Offset(16.1, -40.0), const Offset(-98.1, -40.0)],
396
        ),
397
    );
398
    await gesture.up();
399 400 401 402 403 404 405 406

    // 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(
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
      sliderBox,
      paints
        ..path(
          color: sliderTheme.valueIndicatorColor,
          includes: <Offset>[
            const Offset(0.0, -49.0),
            const Offset(90.0, -49.0),
            const Offset(-24.0, -49.0),
          ],
          excludes: <Offset>[
            const Offset(98.0, -32.0),  // inside full size, outside small
            const Offset(-16.0, -32.0),  // inside full size, outside small
            const Offset(90.1, -49.0),
            const Offset(-24.1, -49.0),
          ],
422
        ),
423
    );
424 425 426 427 428 429 430 431 432
    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(
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
      sliderBox,
      paints
        ..path(
          color: sliderTheme.valueIndicatorColor,
          includes: <Offset>[
            const Offset(0.0, -38.8),
            const Offset(98.0, -38.8),
            const Offset(-16.0, -38.8),
            const Offset(10.0, -23.0), // Inside large, outside scale=1.0
            const Offset(-4.0, -23.0), // Inside large, outside scale=1.0
          ],
          excludes: <Offset>[
            const Offset(98.5, -38.8),
            const Offset(-16.1, -38.8),
          ],
448
        ),
449
    );
450
    await gesture.up();
451
  });
452 453 454 455 456 457 458 459 460 461 462 463 464

  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
        ..rect(rect: Rect.fromLTRB(16.0, 292.0, 208.0, 308.0), color: sliderTheme.activeTrackColor)
465
        ..rect(rect: Rect.fromLTRB(208.0, 292.0, 784.0, 308.0), color: sliderTheme.inactiveTrackColor),
466 467 468 469 470 471 472 473 474 475 476
    );

    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
        ..rect(rect: Rect.fromLTRB(16.0, 292.0, 202.0, 308.0), color: sliderTheme.disabledActiveTrackColor)
477
        ..rect(rect: Rect.fromLTRB(214.0, 292.0, 784.0, 308.0), color: sliderTheme.disabledInactiveTrackColor),
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
    );
  });

  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,
494
      paints..circle(x: 208, y: 300, radius: 7, color: sliderTheme.thumbColor),
495 496 497 498 499 500 501
    );

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

    expect(
      sliderBox,
502
      paints..circle(x: 208, y: 300, radius: 11, color: sliderTheme.disabledThumbColor),
503 504 505 506 507 508 509 510 511 512 513 514 515 516 517
    );
  });

  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,
518
      paints..circle(x: 208, y: 300, radius: 9, color: sliderTheme.thumbColor),
519 520 521 522 523 524 525 526 527
    );

    await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false));
    await tester.pumpAndSettle(); // wait for disable animation
    // Radius should be 6, or 2/3 of 9. 2/3 because the default disabled thumb
    // radius is 4 and the default enabled thumb radius is 6.
    // TODO(clocksmith): This ratio will change once thumb sizes are updated to spec.
    expect(
      sliderBox,
528
      paints..circle(x: 208, y: 300, radius: 6, color: sliderTheme.disabledThumbColor),
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
    );
  });


  testWidgets('The default slider tick mark shape size can be overridden', (WidgetTester tester) async {
    final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
      tickMarkShape: const RoundSliderTickMarkShape(
        tickMarkRadius: 5
      ),
      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
        ..circle(x: 21, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor)
        ..circle(x: 400, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor)
553
        ..circle(x: 779, y: 300, radius: 5, color: sliderTheme.inactiveTickMarkColor),
554 555 556 557 558 559 560 561 562 563
    );

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

    expect(
      sliderBox,
      paints
        ..circle(x: 21, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor)
        ..circle(x: 400, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor)
564
        ..circle(x: 779, y: 300, radius: 5, color: sliderTheme.disabledInactiveTickMarkColor),
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
    );
  });

  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,
590
      ),
591 592
    );
  });
593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611

  // 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,
612
      divisions: 4,
613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629
    ));

    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,
630
      divisions: 4,
631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650
    ));

    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,
651
      divisions: 4,
652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
    ));

    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,
672
      divisions: 4,
673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692
    ));

    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,
693
      divisions: 4,
694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721
    ));

    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,
722
      divisions: 4,
723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738
    ));

    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();
  });
739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762
}

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,
          ),
        ),
      ),
    ),
  );
763
}