navigation_rail_theme_test.dart 15.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  test('copyWith, ==, hashCode basics', () {
    expect(const NavigationRailThemeData(), const NavigationRailThemeData().copyWith());
    expect(const NavigationRailThemeData().hashCode, const NavigationRailThemeData().copyWith().hashCode);
  });

15
  testWidgets('Material3 - Default values are used when no NavigationRail or NavigationRailThemeData properties are specified', (WidgetTester tester) async {
16
    final ThemeData theme = ThemeData.light(useMaterial3: true);
17
    // Material 3 defaults
18 19
    await tester.pumpWidget(
      MaterialApp(
20
        theme: theme,
21 22 23 24 25 26 27 28 29
        home: Scaffold(
          body: NavigationRail(
            selectedIndex: 0,
            destinations: _destinations(),
          ),
        ),
      ),
    );

30
    expect(_railMaterial(tester).color, theme.colorScheme.surface);
31
    expect(_railMaterial(tester).elevation, 0);
32 33
    expect(_destinationSize(tester).width, 80.0);
    expect(_selectedIconTheme(tester).size, 24.0);
34
    expect(_selectedIconTheme(tester).color, theme.colorScheme.onSecondaryContainer);
35 36
    expect(_selectedIconTheme(tester).opacity, null);
    expect(_unselectedIconTheme(tester).size, 24.0);
37
    expect(_unselectedIconTheme(tester).color, theme.colorScheme.onSurfaceVariant);
38 39 40 41 42 43
    expect(_unselectedIconTheme(tester).opacity, null);
    expect(_selectedLabelStyle(tester).fontSize, 14.0);
    expect(_unselectedLabelStyle(tester).fontSize, 14.0);
    expect(_destinationsAlign(tester).alignment, Alignment.topCenter);
    expect(_labelType(tester), NavigationRailLabelType.none);
    expect(find.byType(NavigationIndicator), findsWidgets);
44
    expect(_indicatorDecoration(tester)?.color, theme.colorScheme.secondaryContainer);
45 46 47
    expect(_indicatorDecoration(tester)?.shape, const StadiumBorder());
    final InkResponse inkResponse = tester.allWidgets.firstWhere((Widget object) => object.runtimeType.toString() == '_IndicatorInkWell') as InkResponse;
    expect(inkResponse.customBorder, const StadiumBorder());
48 49
  });

50
  testWidgets('Material2 - Default values are used when no NavigationRail or NavigationRailThemeData properties are specified', (WidgetTester tester) async {
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
    // This test can be removed when `useMaterial3` is deprecated.
    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData.light().copyWith(useMaterial3: false),
        home: Scaffold(
          body: NavigationRail(
            selectedIndex: 0,
            destinations: _destinations(),
          ),
        ),
      ),
    );

    expect(_railMaterial(tester).color, ThemeData().colorScheme.surface);
    expect(_railMaterial(tester).elevation, 0);
    expect(_destinationSize(tester).width, 72.0);
67 68
    expect(_selectedIconTheme(tester).size, 24.0);
    expect(_selectedIconTheme(tester).color, ThemeData().colorScheme.primary);
69
    expect(_selectedIconTheme(tester).opacity, 1.0);
70 71
    expect(_unselectedIconTheme(tester).size, 24.0);
    expect(_unselectedIconTheme(tester).color, ThemeData().colorScheme.onSurface);
72
    expect(_unselectedIconTheme(tester).opacity, 0.64);
73 74 75 76
    expect(_selectedLabelStyle(tester).fontSize, 14.0);
    expect(_unselectedLabelStyle(tester).fontSize, 14.0);
    expect(_destinationsAlign(tester).alignment, Alignment.topCenter);
    expect(_labelType(tester), NavigationRailLabelType.none);
77
    expect(find.byType(NavigationIndicator), findsNothing);
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
  });

  testWidgets('NavigationRailThemeData values are used when no NavigationRail properties are specified', (WidgetTester tester) async {
    const Color backgroundColor = Color(0x00000001);
    const double elevation = 7.0;
    const double selectedIconSize = 25.0;
    const double unselectedIconSize = 23.0;
    const Color selectedIconColor = Color(0x00000002);
    const Color unselectedIconColor = Color(0x00000003);
    const double selectedIconOpacity = 0.99;
    const double unselectedIconOpacity = 0.98;
    const double selectedLabelFontSize = 13.0;
    const double unselectedLabelFontSize = 11.0;
    const double groupAlignment = 0.0;
    const NavigationRailLabelType labelType = NavigationRailLabelType.all;
93 94
    const bool useIndicator = true;
    const Color indicatorColor = Color(0x00000004);
95
    const ShapeBorder indicatorShape = RoundedRectangleBorder();
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: NavigationRailTheme(
            data: const NavigationRailThemeData(
              backgroundColor: backgroundColor,
              elevation: elevation,
              selectedIconTheme: IconThemeData(
                size: selectedIconSize,
                color: selectedIconColor,
                opacity: selectedIconOpacity,
              ),
              unselectedIconTheme: IconThemeData(
                size: unselectedIconSize,
                color: unselectedIconColor,
                opacity: unselectedIconOpacity,
              ),
              selectedLabelTextStyle: TextStyle(fontSize: selectedLabelFontSize),
              unselectedLabelTextStyle: TextStyle(fontSize: unselectedLabelFontSize),
              groupAlignment: groupAlignment,
              labelType: labelType,
118 119
              useIndicator: useIndicator,
              indicatorColor: indicatorColor,
120
              indicatorShape: indicatorShape,
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
            ),
            child: NavigationRail(
              selectedIndex: 0,
              destinations: _destinations(),
            ),
          ),
        ),
      ),
    );

    expect(_railMaterial(tester).color, backgroundColor);
    expect(_railMaterial(tester).elevation, elevation);
    expect(_selectedIconTheme(tester).size, selectedIconSize);
    expect(_selectedIconTheme(tester).color, selectedIconColor);
    expect(_selectedIconTheme(tester).opacity, selectedIconOpacity);
    expect(_unselectedIconTheme(tester).size, unselectedIconSize);
    expect(_unselectedIconTheme(tester).color, unselectedIconColor);
    expect(_unselectedIconTheme(tester).opacity, unselectedIconOpacity);
    expect(_selectedLabelStyle(tester).fontSize, selectedLabelFontSize);
    expect(_unselectedLabelStyle(tester).fontSize, unselectedLabelFontSize);
    expect(_destinationsAlign(tester).alignment, Alignment.center);
    expect(_labelType(tester), labelType);
143 144
    expect(find.byType(NavigationIndicator), findsWidgets);
    expect(_indicatorDecoration(tester)?.color, indicatorColor);
145
    expect(_indicatorDecoration(tester)?.shape, indicatorShape);
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
  });

  testWidgets('NavigationRail values take priority over NavigationRailThemeData values when both properties are specified', (WidgetTester tester) async {
    const Color backgroundColor = Color(0x00000001);
    const double elevation = 7.0;
    const double selectedIconSize = 25.0;
    const double unselectedIconSize = 23.0;
    const Color selectedIconColor = Color(0x00000002);
    const Color unselectedIconColor = Color(0x00000003);
    const double selectedIconOpacity = 0.99;
    const double unselectedIconOpacity = 0.98;
    const double selectedLabelFontSize = 13.0;
    const double unselectedLabelFontSize = 11.0;
    const double groupAlignment = 0.0;
    const NavigationRailLabelType labelType = NavigationRailLabelType.all;
161 162
    const bool useIndicator = true;
    const Color indicatorColor = Color(0x00000004);
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: NavigationRailTheme(
            data: const NavigationRailThemeData(
              backgroundColor: Color(0x00000099),
              elevation: 5,
              selectedIconTheme: IconThemeData(
                size: 31.0,
                color: Color(0x00000098),
                opacity: 0.81,
              ),
              unselectedIconTheme: IconThemeData(
                size: 37.0,
                color: Color(0x00000097),
                opacity: 0.82,
              ),
              selectedLabelTextStyle: TextStyle(fontSize: 9.0),
              unselectedLabelTextStyle: TextStyle(fontSize: 7.0),
              groupAlignment: 1.0,
              labelType: NavigationRailLabelType.selected,
185 186
              useIndicator: false,
              indicatorColor: Color(0x00000096),
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
            ),
            child: NavigationRail(
              selectedIndex: 0,
              destinations: _destinations(),
              backgroundColor: backgroundColor,
              elevation: elevation,
              selectedIconTheme: const IconThemeData(
                size: selectedIconSize,
                color: selectedIconColor,
                opacity: selectedIconOpacity,
              ),
              unselectedIconTheme: const IconThemeData(
                size: unselectedIconSize,
                color: unselectedIconColor,
                opacity: unselectedIconOpacity,
              ),
              selectedLabelTextStyle: const TextStyle(fontSize: selectedLabelFontSize),
              unselectedLabelTextStyle: const TextStyle(fontSize: unselectedLabelFontSize),
              groupAlignment: groupAlignment,
              labelType: labelType,
207 208
              useIndicator: useIndicator,
              indicatorColor: indicatorColor,
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
            ),
          ),
        ),
      ),
    );

    expect(_railMaterial(tester).color, backgroundColor);
    expect(_railMaterial(tester).elevation, elevation);
    expect(_selectedIconTheme(tester).size, selectedIconSize);
    expect(_selectedIconTheme(tester).color, selectedIconColor);
    expect(_selectedIconTheme(tester).opacity, selectedIconOpacity);
    expect(_unselectedIconTheme(tester).size, unselectedIconSize);
    expect(_unselectedIconTheme(tester).color, unselectedIconColor);
    expect(_unselectedIconTheme(tester).opacity, unselectedIconOpacity);
    expect(_selectedLabelStyle(tester).fontSize, selectedLabelFontSize);
    expect(_unselectedLabelStyle(tester).fontSize, unselectedLabelFontSize);
    expect(_destinationsAlign(tester).alignment, Alignment.center);
    expect(_labelType(tester), labelType);
227 228
    expect(find.byType(NavigationIndicator), findsWidgets);
    expect(_indicatorDecoration(tester)?.color, indicatorColor);
229 230
  });

231 232 233 234 235 236 237 238
  // Regression test for https://github.com/flutter/flutter/issues/118618.
  testWidgets('NavigationRailThemeData lerps correctly with null iconThemes', (WidgetTester tester) async {
    final NavigationRailThemeData lerp = NavigationRailThemeData.lerp(const NavigationRailThemeData(), const NavigationRailThemeData(), 0.5)!;

    expect(lerp.selectedIconTheme, isNull);
    expect(lerp.unselectedIconTheme, isNull);
  });

239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
  testWidgets('Default debugFillProperties', (WidgetTester tester) async {
    final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
    const NavigationRailThemeData().debugFillProperties(builder);

    final List<String> description = builder.properties
        .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
        .map((DiagnosticsNode node) => node.toString())
        .toList();

    expect(description, <String>[]);
  });

  testWidgets('Custom debugFillProperties', (WidgetTester tester) async {
    final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
    const NavigationRailThemeData(
      backgroundColor: Color(0x00000099),
      elevation: 5,
      selectedIconTheme: IconThemeData(color: Color(0x00000098)),
      unselectedIconTheme: IconThemeData(color: Color(0x00000097)),
      selectedLabelTextStyle: TextStyle(fontSize: 9.0),
      unselectedLabelTextStyle: TextStyle(fontSize: 7.0),
      groupAlignment: 1.0,
      labelType: NavigationRailLabelType.selected,
262 263
      useIndicator: true,
      indicatorColor: Color(0x00000096),
264
      indicatorShape: CircleBorder(),
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
    ).debugFillProperties(builder);

    final List<String> description = builder.properties
        .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
        .map((DiagnosticsNode node) => node.toString())
        .toList();

    expect(description[0], 'backgroundColor: Color(0x00000099)');
    expect(description[1], 'elevation: 5.0');
    expect(description[2], 'unselectedLabelTextStyle: TextStyle(inherit: true, size: 7.0)');
    expect(description[3], 'selectedLabelTextStyle: TextStyle(inherit: true, size: 9.0)');

    // Ignore instance address for IconThemeData.
    expect(description[4].contains('unselectedIconTheme: IconThemeData'), isTrue);
    expect(description[4].contains('(color: Color(0x00000097))'), isTrue);
    expect(description[5].contains('selectedIconTheme: IconThemeData'), isTrue);
    expect(description[5].contains('(color: Color(0x00000098))'), isTrue);

    expect(description[6], 'groupAlignment: 1.0');
    expect(description[7], 'labelType: NavigationRailLabelType.selected');
285 286
    expect(description[8], 'useIndicator: true');
    expect(description[9], 'indicatorColor: Color(0x00000096)');
287
    expect(description[10], 'indicatorShape: CircleBorder(BorderSide(width: 0.0, style: none))');
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
  });
}

List<NavigationRailDestination> _destinations() {
  return const <NavigationRailDestination>[
    NavigationRailDestination(
      icon: Icon(Icons.favorite_border),
      selectedIcon: Icon(Icons.favorite),
      label: Text('Abc'),
    ),
    NavigationRailDestination(
      icon: Icon(Icons.star_border),
      selectedIcon: Icon(Icons.star),
      label: Text('Def'),
    ),
  ];
}

Material _railMaterial(WidgetTester tester) {
  // The first material is for the rail, and the rest are for the destinations.
  return tester.firstWidget<Material>(
    find.descendant(
      of: find.byType(NavigationRail),
      matching: find.byType(Material),
    ),
  );
}

316

317
ShapeDecoration? _indicatorDecoration(WidgetTester tester) {
318 319 320 321 322
  return tester.firstWidget<Container>(
    find.descendant(
      of: find.byType(NavigationIndicator),
      matching: find.byType(Container),
    ),
323
  ).decoration as ShapeDecoration?;
324 325
}

326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
IconThemeData _selectedIconTheme(WidgetTester tester) {
  return _iconTheme(tester, Icons.favorite);
}

IconThemeData _unselectedIconTheme(WidgetTester tester) {
  return _iconTheme(tester, Icons.star_border);
}

IconThemeData _iconTheme(WidgetTester tester, IconData icon) {
  // The first IconTheme is the one added by the navigation rail.
  return tester.firstWidget<IconTheme>(
    find.ancestor(
      of: find.byIcon(icon),
      matching: find.byType(IconTheme),
    ),
  ).data;
}

TextStyle _selectedLabelStyle(WidgetTester tester) {
  return tester.widget<RichText>(
    find.descendant(
      of: find.text('Abc'),
      matching: find.byType(RichText),
    ),
350
  ).text.style!;
351 352 353 354 355 356 357 358
}

TextStyle _unselectedLabelStyle(WidgetTester tester) {
  return tester.widget<RichText>(
    find.descendant(
      of: find.text('Def'),
      matching: find.byType(RichText),
    ),
359
  ).text.style!;
360 361
}

362 363 364 365 366 367 368 369 370 371
Size _destinationSize(WidgetTester tester) {
  return tester.getSize(
    find.ancestor(
      of: find.byIcon(Icons.favorite),
      matching: find.byType(Material),
    )
    .first
  );
}

372 373 374 375 376 377 378 379 380 381 382 383
Align _destinationsAlign(WidgetTester tester) {
  // The first Expanded widget is the one within the main Column for the rail
  // content.
  return tester.firstWidget<Align>(
    find.descendant(
      of: find.byType(Expanded),
      matching: find.byType(Align),
    ),
  );
}

NavigationRailLabelType _labelType(WidgetTester tester) {
384 385
  if (_visibilityAboveLabel('Abc').evaluate().isNotEmpty && _visibilityAboveLabel('Def').evaluate().isNotEmpty) {
    return _labelVisibility(tester, 'Abc') ? NavigationRailLabelType.selected : NavigationRailLabelType.none;
386 387 388 389 390
  } else {
    return NavigationRailLabelType.all;
  }
}

391
Finder _visibilityAboveLabel(String text) {
392 393
  return find.ancestor(
    of: find.text(text),
394
    matching: find.byType(Visibility),
395 396 397 398
  );
}

// Only valid when labelType != all.
399 400
bool _labelVisibility(WidgetTester tester, String text) {
  final Visibility visibilityWidget = tester.widget<Visibility>(
401 402
    find.ancestor(
      of: find.text(text),
403
      matching: find.byType(Visibility),
404 405
    ),
  );
406
  return visibilityWidget.visible;
407
}