// 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.

// This file is run as part of a reduced test set in CI on Mac and Windows
// machines.
@Tags(<String>['reduced-test-set'])
library;

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

const String _tab1Text = 'tab 1';
const String _tab2Text = 'tab 2';
const String _tab3Text = 'tab 3';

final Key _painterKey = UniqueKey();

const List<Tab> _tabs = <Tab>[
  Tab(text: _tab1Text, icon: Icon(Icons.looks_one)),
  Tab(text: _tab2Text, icon: Icon(Icons.looks_two)),
  Tab(text: _tab3Text, icon: Icon(Icons.looks_3)),
];

final List<SizedBox> _sizedTabs = <SizedBox>[
  SizedBox(key: UniqueKey(), width: 100.0, height: 50.0),
  SizedBox(key: UniqueKey(), width: 100.0, height: 50.0),
];

Widget buildTabBar({
  TabBarTheme? tabBarTheme,
  bool secondaryTabBar = false,
  List<Widget> tabs = _tabs,
  bool isScrollable = false,
  bool useMaterial3 = false,
}) {
  final TabController controller = TabController(
    length: tabs.length,
    vsync: const TestVSync(),
  );
  addTearDown(controller.dispose);

  if (secondaryTabBar) {
    return MaterialApp(
      theme: ThemeData(tabBarTheme: tabBarTheme, useMaterial3: useMaterial3),
      home: Scaffold(
        body: RepaintBoundary(
          key: _painterKey,
          child: TabBar.secondary(
            tabs: tabs,
            isScrollable: isScrollable,
            controller: controller,
          ),
        ),
      ),
    );
  }
  return MaterialApp(
    theme: ThemeData(tabBarTheme: tabBarTheme, useMaterial3: useMaterial3),
    home: Scaffold(
      body: RepaintBoundary(
        key: _painterKey,
        child: TabBar(
          tabs: tabs,
          isScrollable: isScrollable,
          controller: controller,
        ),
      ),
    ),
  );
}


RenderParagraph _getIcon(WidgetTester tester, IconData icon) {
  return tester.renderObject<RenderParagraph>(
    find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)),
  );
}

RenderParagraph _getText(WidgetTester tester, String text) {
  return  tester.renderObject<RenderParagraph>(find.text(text));
}

void main() {
  test('TabBarTheme copyWith, ==, hashCode, defaults', () {
    expect(const TabBarTheme(), const TabBarTheme().copyWith());
    expect(const TabBarTheme().hashCode, const TabBarTheme().copyWith().hashCode);

    expect(const TabBarTheme().indicator, null);
    expect(const TabBarTheme().indicatorColor, null);
    expect(const TabBarTheme().indicatorSize, null);
    expect(const TabBarTheme().dividerColor, null);
    expect(const TabBarTheme().dividerHeight, null);
    expect(const TabBarTheme().labelColor, null);
    expect(const TabBarTheme().labelPadding, null);
    expect(const TabBarTheme().labelStyle, null);
    expect(const TabBarTheme().unselectedLabelColor, null);
    expect(const TabBarTheme().unselectedLabelStyle, null);
    expect(const TabBarTheme().overlayColor, null);
    expect(const TabBarTheme().splashFactory, null);
    expect(const TabBarTheme().mouseCursor, null);
    expect(const TabBarTheme().tabAlignment, null);
  });

  test('TabBarTheme lerp special cases', () {
    const TabBarTheme theme = TabBarTheme();
    expect(identical(TabBarTheme.lerp(theme, theme, 0.5), theme), true);
  });

  testWidgets('Tab bar defaults (primary)', (WidgetTester tester) async {
    // Test default label color and label styles.
    await tester.pumpWidget(buildTabBar(useMaterial3: true));

    final ThemeData theme = ThemeData(useMaterial3: true);
    final RenderParagraph selectedLabel = _getText(tester, _tab1Text);
    expect(selectedLabel.text.style!.fontFamily, equals(theme.textTheme.titleSmall!.fontFamily));
    expect(selectedLabel.text.style!.fontSize, equals(14.0));
    expect(selectedLabel.text.style!.color, equals(theme.colorScheme.primary));
    final RenderParagraph unselectedLabel = _getText(tester, _tab2Text);
    expect(unselectedLabel.text.style!.fontFamily, equals(theme.textTheme.titleSmall!.fontFamily));
    expect(unselectedLabel.text.style!.fontSize, equals(14.0));
    expect(unselectedLabel.text.style!.color, equals(theme.colorScheme.onSurfaceVariant));

    // Test default labelPadding.
    await tester.pumpWidget(buildTabBar(tabs: _sizedTabs, isScrollable: true));

    const double indicatorWeight = 2.0;
    final Rect tabBar = tester.getRect(find.byType(TabBar));
    final Rect tabOneRect = tester.getRect(find.byKey(_sizedTabs[0].key!));
    final Rect tabTwoRect = tester.getRect(find.byKey(_sizedTabs[1].key!));
    const double tabStartOffset = 52.0;

    // Verify tabOne coordinates.
    expect(tabOneRect.left, equals(kTabLabelPadding.left + tabStartOffset));
    expect(tabOneRect.top, equals(kTabLabelPadding.top));
    expect(tabOneRect.bottom, equals(tabBar.bottom - kTabLabelPadding.bottom - indicatorWeight));

    // Verify tabTwo coordinates.
    final double tabTwoRight = tabStartOffset + kTabLabelPadding.horizontal + tabOneRect.width
      + kTabLabelPadding.left + tabTwoRect.width;
    expect(tabTwoRect.right, tabTwoRight);
    expect(tabTwoRect.top, equals(kTabLabelPadding.top));
    expect(tabTwoRect.bottom, equals(tabBar.bottom - kTabLabelPadding.bottom - indicatorWeight));

    // Verify tabOne and tabTwo are separated by right padding of tabOne and left padding of tabTwo.
    expect(tabOneRect.right, equals(tabTwoRect.left - kTabLabelPadding.left - kTabLabelPadding.right));

    // Test default indicator & divider color.
    final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
    expect(
      tabBarBox,
      paints
        ..line(
          color: theme.colorScheme.outlineVariant,
          strokeWidth: 1.0,
        )
        ..rrect(color: theme.colorScheme.primary),
    );
  });

  testWidgets('Tab bar defaults (secondary)', (WidgetTester tester) async {
    // Test default label color and label styles.
    await tester.pumpWidget(buildTabBar(secondaryTabBar: true, useMaterial3: true));

    final ThemeData theme = ThemeData(useMaterial3: true);
    final RenderParagraph selectedLabel = _getText(tester, _tab1Text);
    expect(selectedLabel.text.style!.fontFamily, equals(theme.textTheme.titleSmall!.fontFamily));
    expect(selectedLabel.text.style!.fontSize, equals(14.0));
    expect(selectedLabel.text.style!.color, equals(theme.colorScheme.onSurface));
    final RenderParagraph unselectedLabel = _getText(tester, _tab2Text);
    expect(unselectedLabel.text.style!.fontFamily, equals(theme.textTheme.titleSmall!.fontFamily));
    expect(unselectedLabel.text.style!.fontSize, equals(14.0));
    expect(unselectedLabel.text.style!.color, equals(theme.colorScheme.onSurfaceVariant));

    // Test default labelPadding.
    await tester.pumpWidget(buildTabBar(
      secondaryTabBar: true,
      tabs: _sizedTabs,
      isScrollable: true,
      useMaterial3: true,
    ));

    const double indicatorWeight = 2.0;
    final Rect tabBar = tester.getRect(find.byType(TabBar));
    final Rect tabOneRect = tester.getRect(find.byKey(_sizedTabs[0].key!));
    final Rect tabTwoRect = tester.getRect(find.byKey(_sizedTabs[1].key!));
    const double tabStartOffset = 52.0;

    // Verify tabOne coordinates.
    expect(tabOneRect.left, equals(kTabLabelPadding.left + tabStartOffset));
    expect(tabOneRect.top, equals(kTabLabelPadding.top));
    expect(tabOneRect.bottom, equals(tabBar.bottom - kTabLabelPadding.bottom - indicatorWeight));

    // Verify tabTwo coordinates.
    final double tabTwoRight = tabStartOffset + kTabLabelPadding.horizontal + tabOneRect.width
      + kTabLabelPadding.left + tabTwoRect.width;
    expect(tabTwoRect.right, tabTwoRight);
    expect(tabTwoRect.top, equals(kTabLabelPadding.top));
    expect(tabTwoRect.bottom, equals(tabBar.bottom - kTabLabelPadding.bottom - indicatorWeight));

    // Verify tabOne and tabTwo are separated by right padding of tabOne and left padding of tabTwo.
    expect(tabOneRect.right, equals(tabTwoRect.left - kTabLabelPadding.left - kTabLabelPadding.right));

    // Test default indicator & divider color.
    final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
    expect(
      tabBarBox,
      paints
        ..line(
          color: theme.colorScheme.outlineVariant,
          strokeWidth: 1.0,
        )
        ..line(color: theme.colorScheme.primary),
      );
  });

  testWidgets('Tab bar theme overrides label color (selected)', (WidgetTester tester) async {
    const Color labelColor = Colors.black;
    const TabBarTheme tabBarTheme = TabBarTheme(labelColor: labelColor);

    await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));

    final RenderParagraph tabLabel = _getText(tester, _tab1Text);
    expect(tabLabel.text.style!.color, equals(labelColor));
    final RenderParagraph tabIcon = _getIcon(tester, Icons.looks_one);
    expect(tabIcon.text.style!.color, equals(labelColor));
  });

  testWidgets('Tab bar theme overrides label padding', (WidgetTester tester) async {
    const double topPadding = 10.0;
    const double bottomPadding = 7.0;
    const double rightPadding = 13.0;
    const double leftPadding = 16.0;
    const double indicatorWeight = 2.0; // default value

    const EdgeInsetsGeometry labelPadding = EdgeInsets.fromLTRB(
      leftPadding, topPadding, rightPadding, bottomPadding,
    );

    const TabBarTheme tabBarTheme = TabBarTheme(labelPadding: labelPadding);

    await tester.pumpWidget(buildTabBar(
      tabBarTheme: tabBarTheme,
      tabs: _sizedTabs,
      isScrollable: true,
    ));

    final Rect tabBar = tester.getRect(find.byType(TabBar));
    final Rect tabOneRect = tester.getRect(find.byKey(_sizedTabs[0].key!));
    final Rect tabTwoRect = tester.getRect(find.byKey(_sizedTabs[1].key!));

    // verify coordinates of tabOne
    expect(tabOneRect.left, equals(leftPadding));
    expect(tabOneRect.top, equals(topPadding));
    expect(tabOneRect.bottom, equals(tabBar.bottom - bottomPadding - indicatorWeight));

    // verify coordinates of tabTwo
    expect(tabTwoRect.right, equals(tabBar.width - rightPadding));
    expect(tabTwoRect.top, equals(topPadding));
    expect(tabTwoRect.bottom, equals(tabBar.bottom - bottomPadding - indicatorWeight));

    // verify tabOne and tabTwo are separated by right padding of tabOne and left padding of tabTwo
    expect(tabOneRect.right, equals(tabTwoRect.left - leftPadding - rightPadding));
  });

  testWidgets('Tab bar theme overrides label styles', (WidgetTester tester) async {
    const TextStyle labelStyle = TextStyle(fontFamily: 'foobar');
    const TextStyle unselectedLabelStyle = TextStyle(fontFamily: 'baz');
    const TabBarTheme tabBarTheme = TabBarTheme(
      labelStyle: labelStyle,
      unselectedLabelStyle: unselectedLabelStyle,
    );

    await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));

    final RenderParagraph selectedLabel = _getText(tester, _tab1Text);
    expect(selectedLabel.text.style!.fontFamily, equals(labelStyle.fontFamily));
    final RenderParagraph unselectedLabel = _getText(tester, _tab2Text);
    expect(unselectedLabel.text.style!.fontFamily, equals(unselectedLabelStyle.fontFamily));
  });

  testWidgets('Tab bar theme with just label style specified', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/issues/28784
    const TextStyle labelStyle = TextStyle(fontFamily: 'foobar');
    const TabBarTheme tabBarTheme = TabBarTheme(
      labelStyle: labelStyle,
    );

    await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));

    final RenderParagraph selectedLabel = _getText(tester, _tab1Text);
    expect(selectedLabel.text.style!.fontFamily, equals(labelStyle.fontFamily));
    final RenderParagraph unselectedLabel = _getText(tester, _tab2Text);
    expect(unselectedLabel.text.style!.fontFamily, equals('Roboto'));
    expect(unselectedLabel.text.style!.fontSize, equals(14.0));
    expect(unselectedLabel.text.style!.color, equals(Colors.white.withAlpha(0xB2)));
  });

  testWidgets('Tab bar label styles override theme label styles', (WidgetTester tester) async {
    const TextStyle labelStyle = TextStyle(fontFamily: '1');
    const TextStyle unselectedLabelStyle = TextStyle(fontFamily: '2');
    const TextStyle themeLabelStyle = TextStyle(fontFamily: '3');
    const TextStyle themeUnselectedLabelStyle = TextStyle(fontFamily: '4');
    const TabBarTheme tabBarTheme = TabBarTheme(
      labelStyle: themeLabelStyle,
      unselectedLabelStyle: themeUnselectedLabelStyle,
    );
    final TabController controller = TabController(
      length: _tabs.length,
      vsync: const TestVSync(),
    );
    addTearDown(controller.dispose);

    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(tabBarTheme: tabBarTheme),
        home: Scaffold(
          body: TabBar(
            tabs: _tabs,
            controller: controller,
            labelStyle: labelStyle,
            unselectedLabelStyle: unselectedLabelStyle,
          ),
        ),
      ),
    );

    final RenderParagraph selectedLabel = _getText(tester, _tab1Text);
    expect(selectedLabel.text.style!.fontFamily, equals(labelStyle.fontFamily));
    final RenderParagraph unselectedLabel = _getText(tester, _tab2Text);
    expect(unselectedLabel.text.style!.fontFamily, equals(unselectedLabelStyle.fontFamily));
  });

  testWidgets('Material2 - Tab bar label padding overrides theme label padding', (WidgetTester tester) async {
    const double verticalPadding = 10.0;
    const double horizontalPadding = 10.0;
    const EdgeInsetsGeometry labelPadding = EdgeInsets.symmetric(
      vertical: verticalPadding,
      horizontal: horizontalPadding,
    );

    const double verticalThemePadding = 20.0;
    const double horizontalThemePadding = 20.0;
    const EdgeInsetsGeometry themeLabelPadding = EdgeInsets.symmetric(
      vertical: verticalThemePadding,
      horizontal: horizontalThemePadding,
    );

    const double indicatorWeight = 2.0; // default value

    const TabBarTheme tabBarTheme = TabBarTheme(labelPadding: themeLabelPadding);

    final TabController controller = TabController(
      length: _sizedTabs.length,
      vsync: const TestVSync(),
    );
    addTearDown(controller.dispose);

    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(tabBarTheme: tabBarTheme, useMaterial3: false),
        home: Scaffold(body:
          RepaintBoundary(
            key: _painterKey,
            child: TabBar(
              tabs: _sizedTabs,
              isScrollable: true,
              controller: controller,
              labelPadding: labelPadding,
            ),
          ),
        ),
      ),
    );

    final Rect tabBar = tester.getRect(find.byType(TabBar));
    final Rect tabOneRect = tester.getRect(find.byKey(_sizedTabs[0].key!));
    final Rect tabTwoRect = tester.getRect(find.byKey(_sizedTabs[1].key!));

    // verify coordinates of tabOne
    expect(tabOneRect.left, equals(horizontalPadding));
    expect(tabOneRect.top, equals(verticalPadding));
    expect(tabOneRect.bottom, equals(tabBar.bottom - verticalPadding - indicatorWeight));

    // verify coordinates of tabTwo
    expect(tabTwoRect.right, equals(tabBar.width - horizontalPadding));
    expect(tabTwoRect.top, equals(verticalPadding));
    expect(tabTwoRect.bottom, equals(tabBar.bottom - verticalPadding - indicatorWeight));

    // verify tabOne and tabTwo are separated by 2x horizontalPadding
    expect(tabOneRect.right, equals(tabTwoRect.left - (2 * horizontalPadding)));
  });

  testWidgets('Material3 - Tab bar label padding overrides theme label padding', (WidgetTester tester) async {
    const double tabStartOffset = 52.0;
    const double verticalPadding = 10.0;
    const double horizontalPadding = 10.0;
    const EdgeInsetsGeometry labelPadding = EdgeInsets.symmetric(
      vertical: verticalPadding,
      horizontal: horizontalPadding,
    );

    const double verticalThemePadding = 20.0;
    const double horizontalThemePadding = 20.0;
    const EdgeInsetsGeometry themeLabelPadding = EdgeInsets.symmetric(
      vertical: verticalThemePadding,
      horizontal: horizontalThemePadding,
    );

    const double indicatorWeight = 2.0; // default value

    const TabBarTheme tabBarTheme = TabBarTheme(labelPadding: themeLabelPadding);

    final TabController controller = TabController(
      length: _sizedTabs.length,
      vsync: const TestVSync(),
    );
    addTearDown(controller.dispose);

    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(tabBarTheme: tabBarTheme, useMaterial3: true),
        home: Scaffold(body:
          RepaintBoundary(
            key: _painterKey,
            child: TabBar(
              tabs: _sizedTabs,
              isScrollable: true,
              controller: controller,
              labelPadding: labelPadding,
            ),
          ),
        ),
      ),
    );

    final Rect tabBar = tester.getRect(find.byType(TabBar));
    final Rect tabOneRect = tester.getRect(find.byKey(_sizedTabs[0].key!));
    final Rect tabTwoRect = tester.getRect(find.byKey(_sizedTabs[1].key!));

    // verify coordinates of tabOne
    expect(tabOneRect.left, equals(horizontalPadding + tabStartOffset));
    expect(tabOneRect.top, equals(verticalPadding));
    expect(tabOneRect.bottom, equals(tabBar.bottom - verticalPadding - indicatorWeight));

    // verify coordinates of tabTwo
    expect(tabTwoRect.right, equals(tabStartOffset + horizontalThemePadding + tabOneRect.width + tabTwoRect.width + (horizontalThemePadding / 2)));
    expect(tabTwoRect.top, equals(verticalPadding));
    expect(tabTwoRect.bottom, equals(tabBar.bottom - verticalPadding - indicatorWeight));

    // verify tabOne and tabTwo are separated by 2x horizontalPadding
    expect(tabOneRect.right, equals(tabTwoRect.left - (2 * horizontalPadding)));
  });

  testWidgets('Tab bar theme overrides label color (unselected)', (WidgetTester tester) async {
    const Color unselectedLabelColor = Colors.black;
    const TabBarTheme tabBarTheme = TabBarTheme(unselectedLabelColor: unselectedLabelColor);

    await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));

    final RenderParagraph textRenderObject = tester.renderObject<RenderParagraph>(find.text(_tab2Text));
    expect(textRenderObject.text.style!.color, equals(unselectedLabelColor));
    final RenderParagraph iconRenderObject = _getIcon(tester, Icons.looks_two);
    expect(iconRenderObject.text.style!.color, equals(unselectedLabelColor));
  });

  testWidgets('Tab bar default tab indicator size (primary)', (WidgetTester tester) async {
    await tester.pumpWidget(buildTabBar(useMaterial3: true, isScrollable: true));

    await expectLater(
      find.byKey(_painterKey),
      matchesGoldenFile('tab_bar.default.tab_indicator_size.png'),
    );
  });

  testWidgets('Tab bar default tab indicator size (secondary)', (WidgetTester tester) async {
    await tester.pumpWidget(buildTabBar(useMaterial3: true, isScrollable: true));

    await expectLater(
      find.byKey(_painterKey),
      matchesGoldenFile('tab_bar_secondary.default.tab_indicator_size.png'),
    );
  });

  testWidgets('Tab bar theme overrides tab indicator size (tab)', (WidgetTester tester) async {
    const TabBarTheme tabBarTheme = TabBarTheme(indicatorSize: TabBarIndicatorSize.tab);

    await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));

    await expectLater(
      find.byKey(_painterKey),
      matchesGoldenFile('tab_bar_theme.tab_indicator_size_tab.png'),
    );
  });

  testWidgets('Tab bar theme overrides tab indicator size (label)', (WidgetTester tester) async {
    const TabBarTheme tabBarTheme = TabBarTheme(indicatorSize: TabBarIndicatorSize.label);

    await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));

    await expectLater(
      find.byKey(_painterKey),
      matchesGoldenFile('tab_bar_theme.tab_indicator_size_label.png'),
    );
  });

  testWidgets('Tab bar theme overrides tab mouse cursor', (WidgetTester tester) async {
    const TabBarTheme tabBarTheme = TabBarTheme(mouseCursor: MaterialStateMouseCursor.textable);

    await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));

    final Offset tabBar = tester.getCenter(
      find.ancestor(of: find.text('tab 1'),matching: find.byType(TabBar)),
    );
    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
    await gesture.addPointer();
    await gesture.moveTo(tabBar);
    await tester.pumpAndSettle();
    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
  });

  testWidgets('Tab bar theme - custom tab indicator', (WidgetTester tester) async {
    final TabBarTheme tabBarTheme = TabBarTheme(
      indicator: BoxDecoration(
        border: Border.all(),
      ),
    );

    await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));

    await expectLater(
      find.byKey(_painterKey),
      matchesGoldenFile('tab_bar_theme.custom_tab_indicator.png'),
    );
  });

  testWidgets('Tab bar theme - beveled rect indicator', (WidgetTester tester) async {
    const TabBarTheme tabBarTheme = TabBarTheme(
      indicator: ShapeDecoration(
        shape: BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20.0))),
        color: Colors.black,
      ),
    );

    await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));

    await expectLater(
      find.byKey(_painterKey),
      matchesGoldenFile('tab_bar_theme.beveled_rect_indicator.png'),
    );
  });

  testWidgets('TabAlignment.fill from TabBarTheme only supports non-scrollable tab bar', (WidgetTester tester) async {
    const TabBarTheme tabBarTheme = TabBarTheme(tabAlignment: TabAlignment.fill);

    // Test TabAlignment.fill from TabBarTheme with non-scrollable tab bar.
    await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));

    expect(tester.takeException(), isNull);

    // Test TabAlignment.fill from TabBarTheme with scrollable tab bar.
    await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme, isScrollable: true));

    expect(tester.takeException(), isAssertionError);
  });

  testWidgets(
    'TabAlignment.start & TabAlignment.startOffset from TabBarTheme only supports scrollable tab bar',
    (WidgetTester tester) async {
      TabBarTheme tabBarTheme = const TabBarTheme(tabAlignment: TabAlignment.start);

      // Test TabAlignment.start from TabBarTheme with scrollable tab bar.
      await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme, isScrollable: true));

      expect(tester.takeException(), isNull);

      // Test TabAlignment.start from TabBarTheme with non-scrollable tab bar.
      await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));

      expect(tester.takeException(), isAssertionError);

      tabBarTheme = const TabBarTheme(tabAlignment: TabAlignment.startOffset);

      // Test TabAlignment.startOffset from TabBarTheme with scrollable tab bar.
      await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme, isScrollable: true));

      expect(tester.takeException(), isNull);

      // Test TabAlignment.startOffset from TabBarTheme with non-scrollable tab bar.
      await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));

      expect(tester.takeException(), isAssertionError);
  });

  testWidgets('TabBarTheme.indicatorSize provides correct tab indicator (primary)', (WidgetTester tester) async {
    final ThemeData theme = ThemeData(
      tabBarTheme: const TabBarTheme(indicatorSize: TabBarIndicatorSize.tab),
      useMaterial3: true,
    );
    final List<Widget> tabs = List<Widget>.generate(4, (int index) {
      return Tab(text: 'Tab $index');
    });

    final TabController controller = TabController(
      vsync: const TestVSync(),
      length: tabs.length,
    );
    addTearDown(controller.dispose);

    await tester.pumpWidget(
      MaterialApp(
        theme: theme,
        home: Scaffold(
          body: Container(
            alignment: Alignment.topLeft,
            child: TabBar(
              controller: controller,
              tabs: tabs,
            ),
          ),
        ),
      ),
    );

    final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
    expect(tabBarBox.size.height, 48.0);

    const double indicatorWeight = 2.0;
    const double indicatorY = 48 - (indicatorWeight / 2.0);
    const double indicatorLeft =  indicatorWeight / 2.0;
    const double indicatorRight = 200.0 - (indicatorWeight / 2.0);

    expect(
      tabBarBox,
      paints
        // Divider.
        ..line(
          color: theme.colorScheme.outlineVariant,
          strokeWidth: 1.0,
        )
        // Tab indicator.
        ..line(
          color: theme.colorScheme.primary,
          strokeWidth: indicatorWeight,
          p1: const Offset(indicatorLeft, indicatorY),
          p2: const Offset(indicatorRight, indicatorY),
        ),
    );
  });

  testWidgets('TabBarTheme.indicatorSize provides correct tab indicator (secondary)', (WidgetTester tester) async {
    final ThemeData theme = ThemeData(
      tabBarTheme: const TabBarTheme(indicatorSize: TabBarIndicatorSize.label),
      useMaterial3: true,
    );
    final List<Widget> tabs = List<Widget>.generate(4, (int index) {
      return Tab(text: 'Tab $index');
    });

    final TabController controller = TabController(
      vsync: const TestVSync(),
      length: tabs.length,
    );
    addTearDown(controller.dispose);

    await tester.pumpWidget(
      MaterialApp(
        theme: theme,
        home: Scaffold(
          body: Container(
            alignment: Alignment.topLeft,
            child: TabBar.secondary(
              controller: controller,
              tabs: tabs,
            ),
          ),
        ),
      ),
    );

    final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
    expect(tabBarBox.size.height, 48.0);

    const double indicatorWeight = 2.0;
    const double indicatorY = 48 - (indicatorWeight / 2.0);

    expect(
      tabBarBox,
      paints
        // Divider.
        ..line(
          color: theme.colorScheme.outlineVariant,
          strokeWidth: 1.0,
        )
        // Tab indicator
        ..line(
          color: theme.colorScheme.primary,
          strokeWidth: indicatorWeight,
          p1: const Offset(65.75, indicatorY),
          p2: const Offset(134.25, indicatorY),
        ),
    );
  });

  testWidgets('TabBar divider can use TabBarTheme.dividerColor & TabBarTheme.dividerHeight', (WidgetTester tester) async {
    const Color dividerColor = Color(0xff00ff00);
    const double dividerHeight = 10.0;

    final TabController controller = TabController(
      length: 3,
      vsync: const TestVSync(),
    );
    addTearDown(controller.dispose);

    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(
          tabBarTheme: const TabBarTheme(
            dividerColor: dividerColor,
            dividerHeight: dividerHeight,
          ),
          useMaterial3: true,
        ),
        home: Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              controller: controller,
              tabs: const <Widget>[
                Tab(text: 'Tab 1'),
                Tab(text: 'Tab 2'),
                Tab(text: 'Tab 3'),
              ],
            ),
          ),
        ),
      ),
    );

    final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
    // Test divider color.
    expect(tabBarBox, paints..line(color: dividerColor, strokeWidth: dividerHeight));
  });

  testWidgets('dividerColor & dividerHeight overrides TabBarTheme.dividerColor', (WidgetTester tester) async {
    const Color dividerColor = Color(0xff0000ff);
    const double dividerHeight = 8.0;

    final TabController controller = TabController(
      length: 3,
      vsync: const TestVSync(),
    );
    addTearDown(controller.dispose);

    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(
          useMaterial3: true,
          tabBarTheme: const TabBarTheme(
            dividerColor: Colors.pink,
            dividerHeight: 5.0,
          ),
        ),
        home: Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              dividerColor: dividerColor,
              dividerHeight: dividerHeight,
              controller: controller,
              tabs: const <Widget>[
                Tab(text: 'Tab 1'),
                Tab(text: 'Tab 2'),
                Tab(text: 'Tab 3'),
              ],
            ),
          ),
        ),
      ),
    );

    final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
    // Test divider color.
    expect(tabBarBox, paints..line(color: dividerColor, strokeWidth: dividerHeight));
  });

  testWidgets('TabBar respects TabBarTheme.tabAlignment', (WidgetTester tester) async {
    final TabController controller1 = TabController(
      length: 2,
      vsync: const TestVSync(),
    );
    addTearDown(controller1.dispose);

    // Test non-scrollable tab bar.
    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(
          tabBarTheme: const TabBarTheme(tabAlignment: TabAlignment.center),
          useMaterial3: true,
        ),
        home: Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              controller: controller1,
              tabs: const <Widget>[
                Tab(text: 'Tab 1'),
                Tab(text: 'Tab 3'),
              ],
            ),
          ),
        ),
      ),
    );

    const double availableWidth = 800.0;
    Rect tabOneRect = tester.getRect(find.byType(Tab).first);
    Rect tabTwoRect = tester.getRect(find.byType(Tab).last);

    double tabOneLeft = (availableWidth / 2) - tabOneRect.width - kTabLabelPadding.left;
    expect(tabOneRect.left, equals(tabOneLeft));
    double tabTwoRight = (availableWidth / 2) + tabTwoRect.width + kTabLabelPadding.right;
    expect(tabTwoRect.right, equals(tabTwoRight));

    final TabController controller2 = TabController(
      length: 2,
      vsync: const TestVSync(),
    );
    addTearDown(controller2.dispose);

    // Test scrollable tab bar.
    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(
          tabBarTheme: const TabBarTheme(tabAlignment: TabAlignment.start),
          useMaterial3: true,
        ),
        home: Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              isScrollable: true,
              controller: controller2,
              tabs: const <Widget>[
                Tab(text: 'Tab 1'),
                Tab(text: 'Tab 3'),
              ],
            ),
          ),
        ),
      ),
    );
    await tester.pumpAndSettle();

    tabOneRect = tester.getRect(find.byType(Tab).first);
    tabTwoRect = tester.getRect(find.byType(Tab).last);

    tabOneLeft = kTabLabelPadding.left;
    expect(tabOneRect.left, equals(tabOneLeft));
    tabTwoRight = kTabLabelPadding.horizontal + tabOneRect.width + kTabLabelPadding.left + tabTwoRect.width;
    expect(tabTwoRect.right, equals(tabTwoRight));
  });

  testWidgets('TabBar.tabAlignment overrides TabBarTheme.tabAlignment', (WidgetTester tester) async {
    final TabController controller1 = TabController(
      length: 2,
      vsync: const TestVSync(),
    );
    addTearDown(controller1.dispose);

    /// Test non-scrollable tab bar.
    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(
          tabBarTheme: const TabBarTheme(tabAlignment: TabAlignment.fill),
          useMaterial3: true,
        ),
        home: Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              tabAlignment: TabAlignment.center,
              controller: controller1,
              tabs: const <Widget>[
                Tab(text: 'Tab 1'),
                Tab(text: 'Tab 3'),
              ],
            ),
          ),
        ),
      ),
    );

    const double availableWidth = 800.0;
    Rect tabOneRect = tester.getRect(find.byType(Tab).first);
    Rect tabTwoRect = tester.getRect(find.byType(Tab).last);

    double tabOneLeft = (availableWidth / 2) - tabOneRect.width - kTabLabelPadding.left;
    expect(tabOneRect.left, equals(tabOneLeft));
    double tabTwoRight = (availableWidth / 2) + tabTwoRect.width + kTabLabelPadding.right;
    expect(tabTwoRect.right, equals(tabTwoRight));

    final TabController controller2 = TabController(
      length: 2,
      vsync: const TestVSync(),
    );
    addTearDown(controller2.dispose);

    /// Test scrollable tab bar.
    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(
          tabBarTheme: const TabBarTheme(tabAlignment: TabAlignment.center),
          useMaterial3: true,
        ),
        home: Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              isScrollable: true,
              tabAlignment: TabAlignment.start,
              controller: controller2,
              tabs: const <Widget>[
                Tab(text: 'Tab 1'),
                Tab(text: 'Tab 3'),
              ],
            ),
          ),
        ),
      ),
    );
    await tester.pumpAndSettle();

    tabOneRect = tester.getRect(find.byType(Tab).first);
    tabTwoRect = tester.getRect(find.byType(Tab).last);

    tabOneLeft = kTabLabelPadding.left;
    expect(tabOneRect.left, equals(tabOneLeft));
    tabTwoRight = kTabLabelPadding.horizontal + tabOneRect.width + kTabLabelPadding.left + tabTwoRect.width;
    expect(tabTwoRect.right, equals(tabTwoRight));
  });

  testWidgets(
    'TabBar labels use colors from TabBarTheme.labelStyle & TabBarTheme.unselectedLabelStyle',
    (WidgetTester tester) async {
      const TextStyle labelStyle = TextStyle(
        color: Color(0xff0000ff),
        fontStyle: FontStyle.italic,
      );
      const TextStyle unselectedLabelStyle = TextStyle(
        color: Color(0x950000ff),
        fontStyle: FontStyle.italic,
      );
      const TabBarTheme tabBarTheme = TabBarTheme(
        labelStyle: labelStyle,
        unselectedLabelStyle: unselectedLabelStyle,
      );

      // Test tab bar with TabBarTheme labelStyle & unselectedLabelStyle.
      await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));

      final IconThemeData selectedTabIcon = IconTheme.of(tester.element(find.text(_tab1Text)));
      final IconThemeData unselectedTabIcon = IconTheme.of(tester.element(find.text(_tab2Text)));
      final TextStyle selectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab1Text))
        .text.style!;
      final TextStyle unselectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab2Text))
        .text.style!;

      // Selected tab should use labelStyle color.
      expect(selectedTabIcon.color, labelStyle.color);
      expect(selectedTextStyle.color, labelStyle.color);
      expect(selectedTextStyle.fontStyle, labelStyle.fontStyle);
      // Unselected tab should use unselectedLabelStyle color.
      expect(unselectedTabIcon.color, unselectedLabelStyle.color);
      expect(unselectedTextStyle.color, unselectedLabelStyle.color);
      expect(unselectedTextStyle.fontStyle, unselectedLabelStyle.fontStyle);
  });

  testWidgets(
    "TabBarTheme's labelColor & unselectedLabelColor override labelStyle & unselectedLabelStyle colors",
    (WidgetTester tester) async {
      const Color labelColor = Color(0xfff00000);
      const Color unselectedLabelColor = Color(0x95ff0000);
      const TextStyle labelStyle = TextStyle(
        color: Color(0xff0000ff),
        fontStyle: FontStyle.italic,
      );
      const TextStyle unselectedLabelStyle = TextStyle(
        color: Color(0x950000ff),
        fontStyle: FontStyle.italic,
      );
      TabBarTheme tabBarTheme = const TabBarTheme(
        labelStyle: labelStyle,
        unselectedLabelStyle: unselectedLabelStyle,
      );

      await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));

      // Test tab bar with TabBarTheme labelStyle & unselectedLabelStyle.
      await tester.pumpWidget(buildTabBar());

      IconThemeData selectedTabIcon = IconTheme.of(tester.element(find.text(_tab1Text)));
      IconThemeData unselectedTabIcon = IconTheme.of(tester.element(find.text(_tab2Text)));
      TextStyle selectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab1Text))
        .text.style!;
      TextStyle unselectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab2Text))
        .text.style!;

      // Selected tab should use the labelStyle color.
      expect(selectedTabIcon.color, labelStyle.color);
      expect(selectedTextStyle.color, labelStyle.color);
      expect(selectedTextStyle.fontStyle, labelStyle.fontStyle);
      // Unselected tab should use the unselectedLabelStyle color.
      expect(unselectedTabIcon.color, unselectedLabelStyle.color);
      expect(unselectedTextStyle.color, unselectedLabelStyle.color);
      expect(unselectedTextStyle.fontStyle, unselectedLabelStyle.fontStyle);

      // Update the TabBarTheme with labelColor & unselectedLabelColor.
      tabBarTheme = const TabBarTheme(
        labelColor: labelColor,
        unselectedLabelColor: unselectedLabelColor,
        labelStyle: labelStyle,
        unselectedLabelStyle: unselectedLabelStyle,
      );
      await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));
      await tester.pumpAndSettle();

      selectedTabIcon = IconTheme.of(tester.element(find.text(_tab1Text)));
      unselectedTabIcon = IconTheme.of(tester.element(find.text(_tab2Text)));
      selectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab1Text)).text.style!;
      unselectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab2Text)).text.style!;

      // Selected tab should use the labelColor.
      expect(selectedTabIcon.color, labelColor);
      expect(selectedTextStyle.color, labelColor);
      expect(selectedTextStyle.fontStyle, labelStyle.fontStyle);
      // Unselected tab should use the unselectedLabelColor.
      expect(unselectedTabIcon.color, unselectedLabelColor);
      expect(unselectedTextStyle.color, unselectedLabelColor);
      expect(unselectedTextStyle.fontStyle, unselectedLabelStyle.fontStyle);
  });

  testWidgets(
    "TabBarTheme's labelColor & unselectedLabelColor override TabBar.labelStyle & TabBar.unselectedLabelStyle colors",
    (WidgetTester tester) async {
      const Color labelColor = Color(0xfff00000);
      const Color unselectedLabelColor = Color(0x95ff0000);
      const TextStyle labelStyle = TextStyle(
        color: Color(0xff0000ff),
        fontStyle: FontStyle.italic,
      );
      const TextStyle unselectedLabelStyle = TextStyle(
        color: Color(0x950000ff),
        fontStyle: FontStyle.italic,
      );

      Widget buildTabBar({TabBarTheme? tabBarTheme}) {
        return MaterialApp(
          theme: ThemeData(tabBarTheme: tabBarTheme),
          home: const Material(
            child: DefaultTabController(
              length: 2,
              child: TabBar(
                labelStyle: labelStyle,
                unselectedLabelStyle: unselectedLabelStyle,
                tabs: <Widget>[
                  Tab(text: _tab1Text),
                  Tab(text: _tab2Text),
                ],
              ),
            ),
          ),
        );
      }

      // Test tab bar with [TabBar.labelStyle] & [TabBar.unselectedLabelStyle].
      await tester.pumpWidget(buildTabBar());

      IconThemeData selectedTabIcon = IconTheme.of(tester.element(find.text(_tab1Text)));
      IconThemeData unselectedTabIcon = IconTheme.of(tester.element(find.text(_tab2Text)));
      TextStyle selectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab1Text))
        .text.style!;
      TextStyle unselectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab2Text))
        .text.style!;

      // Selected tab should use the [TabBar.labelStyle] color.
      expect(selectedTabIcon.color, labelStyle.color);
      expect(selectedTextStyle.color, labelStyle.color);
      expect(selectedTextStyle.fontStyle, labelStyle.fontStyle);
      // Unselected tab should use the [TabBar.unselectedLabelStyle] color.
      expect(unselectedTabIcon.color, unselectedLabelStyle.color);
      expect(unselectedTextStyle.color, unselectedLabelStyle.color);
      expect(unselectedTextStyle.fontStyle, unselectedLabelStyle.fontStyle);

      // Add TabBarTheme with labelColor & unselectedLabelColor.
      await tester.pumpWidget(buildTabBar(tabBarTheme: const TabBarTheme(
        labelColor: labelColor,
        unselectedLabelColor: unselectedLabelColor,
      )));
      await tester.pumpAndSettle();

      selectedTabIcon = IconTheme.of(tester.element(find.text(_tab1Text)));
      unselectedTabIcon = IconTheme.of(tester.element(find.text(_tab2Text)));
      selectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab1Text)).text.style!;
      unselectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab2Text)).text.style!;

      // Selected tab should use the [TabBarTheme.labelColor].
      expect(selectedTabIcon.color, labelColor);
      expect(selectedTextStyle.color, labelColor);
      expect(selectedTextStyle.fontStyle, labelStyle.fontStyle);
      // Unselected tab should use the [TabBarTheme.unselectedLabelColor].
      expect(unselectedTabIcon.color, unselectedLabelColor);
      expect(unselectedTextStyle.color, unselectedLabelColor);
      expect(unselectedTextStyle.fontStyle, unselectedLabelStyle.fontStyle);
  });

  group('Material 2', () {
    // These tests are only relevant for Material 2. Once Material 2
    // support is deprecated and the APIs are removed, these tests
    // can be deleted.

    testWidgets('Tab bar defaults (primary)', (WidgetTester tester) async {
    // Test default label color and label styles.
      await tester.pumpWidget(buildTabBar());

      final ThemeData theme = ThemeData(useMaterial3: false);
      final RenderParagraph selectedLabel = _getText(tester, _tab1Text);
      expect(selectedLabel.text.style!.fontFamily, equals('Roboto'));
      expect(selectedLabel.text.style!.fontSize, equals(14.0));
      expect(selectedLabel.text.style!.color, equals(Colors.white));
      final RenderParagraph unselectedLabel = _getText(tester, _tab2Text);
      expect(unselectedLabel.text.style!.fontFamily, equals('Roboto'));
      expect(unselectedLabel.text.style!.fontSize, equals(14.0));
      expect(unselectedLabel.text.style!.color, equals(Colors.white.withAlpha(0xB2)));

      // Test default labelPadding.
      await tester.pumpWidget(buildTabBar(tabs: _sizedTabs, isScrollable: true));

      const double indicatorWeight = 2.0;
      final Rect tabBar = tester.getRect(find.byType(TabBar));
      final Rect tabOneRect = tester.getRect(find.byKey(_sizedTabs[0].key!));
      final Rect tabTwoRect = tester.getRect(find.byKey(_sizedTabs[1].key!));

      // Verify tabOne coordinates.
      expect(tabOneRect.left, equals(kTabLabelPadding.left));
      expect(tabOneRect.top, equals(kTabLabelPadding.top));
      expect(tabOneRect.bottom, equals(tabBar.bottom - kTabLabelPadding.bottom - indicatorWeight));

      // Verify tabTwo coordinates.
      expect(tabTwoRect.right, equals(tabBar.width - kTabLabelPadding.right));
      expect(tabTwoRect.top, equals(kTabLabelPadding.top));
      expect(tabTwoRect.bottom, equals(tabBar.bottom - kTabLabelPadding.bottom - indicatorWeight));

      // Verify tabOne and tabTwo is separated by right padding of tabOne and left padding of tabTwo.
      expect(tabOneRect.right, equals(tabTwoRect.left - kTabLabelPadding.left - kTabLabelPadding.right));

      // Test default indicator color.
      final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
      expect(tabBarBox, paints..line(color: theme.indicatorColor));
    });

    testWidgets('Tab bar defaults (secondary)', (WidgetTester tester) async {
      // Test default label color and label styles.
      await tester.pumpWidget(buildTabBar(secondaryTabBar: true));

      final ThemeData theme = ThemeData(useMaterial3: false);
      final RenderParagraph selectedLabel = _getText(tester, _tab1Text);
      expect(selectedLabel.text.style!.fontFamily, equals('Roboto'));
      expect(selectedLabel.text.style!.fontSize, equals(14.0));
      expect(selectedLabel.text.style!.color, equals(Colors.white));
      final RenderParagraph unselectedLabel = _getText(tester, _tab2Text);
      expect(unselectedLabel.text.style!.fontFamily, equals('Roboto'));
      expect(unselectedLabel.text.style!.fontSize, equals(14.0));
      expect(unselectedLabel.text.style!.color, equals(Colors.white.withAlpha(0xB2)));

      // Test default labelPadding.
      await tester.pumpWidget(buildTabBar(tabs: _sizedTabs, isScrollable: true));

      const double indicatorWeight = 2.0;
      final Rect tabBar = tester.getRect(find.byType(TabBar));
      final Rect tabOneRect = tester.getRect(find.byKey(_sizedTabs[0].key!));
      final Rect tabTwoRect = tester.getRect(find.byKey(_sizedTabs[1].key!));

      // Verify tabOne coordinates.
      expect(tabOneRect.left, equals(kTabLabelPadding.left));
      expect(tabOneRect.top, equals(kTabLabelPadding.top));
      expect(tabOneRect.bottom, equals(tabBar.bottom - kTabLabelPadding.bottom - indicatorWeight));

      // Verify tabTwo coordinates.
      expect(tabTwoRect.right, equals(tabBar.width - kTabLabelPadding.right));
      expect(tabTwoRect.top, equals(kTabLabelPadding.top));
      expect(tabTwoRect.bottom, equals(tabBar.bottom - kTabLabelPadding.bottom - indicatorWeight));

      // Verify tabOne and tabTwo are separated by right padding of tabOne and left padding of tabTwo.
      expect(tabOneRect.right, equals(tabTwoRect.left - kTabLabelPadding.left - kTabLabelPadding.right));

      // Test default indicator color.
      final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
      expect(tabBarBox, paints..line(color: theme.indicatorColor));
    });

    testWidgets('Tab bar default tab indicator size', (WidgetTester tester) async {
      await tester.pumpWidget(buildTabBar());

      await expectLater(
        find.byKey(_painterKey),
        matchesGoldenFile('tab_bar.m2.default.tab_indicator_size.png'),
      );
    });

    testWidgets('TabBarTheme.indicatorSize provides correct tab indicator (primary)', (WidgetTester tester) async {
      final ThemeData theme = ThemeData(
        tabBarTheme: const TabBarTheme(indicatorSize: TabBarIndicatorSize.tab),
        useMaterial3: false,
      );
      final List<Widget> tabs = List<Widget>.generate(4, (int index) {
        return Tab(text: 'Tab $index');
      });

      final TabController controller = TabController(
        vsync: const TestVSync(),
        length: tabs.length,
      );
      addTearDown(controller.dispose);

      await tester.pumpWidget(
        MaterialApp(
          theme: theme,
          home: Scaffold(
            body: Container(
              alignment: Alignment.topLeft,
              child: TabBar(
                controller: controller,
                tabs: tabs,
              ),
            ),
          ),
        ),
      );

      final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
      expect(tabBarBox.size.height, 48.0);

      const double indicatorWeight = 2.0;
      const double indicatorY = 48 - (indicatorWeight / 2.0);
      const double indicatorLeft =  indicatorWeight / 2.0;
      const double indicatorRight = 200.0 - (indicatorWeight / 2.0);

      expect(
        tabBarBox,
        paints
          // Tab indicator
          ..line(
            color: theme.indicatorColor,
            strokeWidth: indicatorWeight,
            p1: const Offset(indicatorLeft, indicatorY),
            p2: const Offset(indicatorRight, indicatorY),
          ),
      );
    });

    testWidgets('TabBarTheme.indicatorSize provides correct tab indicator (secondary)', (WidgetTester tester) async {
      final ThemeData theme = ThemeData(
        tabBarTheme: const TabBarTheme(indicatorSize: TabBarIndicatorSize.label),
        useMaterial3: false,
      );
      final List<Widget> tabs = List<Widget>.generate(4, (int index) {
        return Tab(text: 'Tab $index');
      });

      final TabController controller = TabController(
        vsync: const TestVSync(),
        length: tabs.length,
      );
      addTearDown(controller.dispose);

      await tester.pumpWidget(
        MaterialApp(
          theme: theme,
          home: Scaffold(
            body: Container(
              alignment: Alignment.topLeft,
              child: TabBar.secondary(
                controller: controller,
                tabs: tabs,
              ),
            ),
          ),
        ),
      );

      final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
      expect(tabBarBox.size.height, 48.0);

      const double indicatorWeight = 2.0;
      const double indicatorY = 48 - (indicatorWeight / 2.0);

      expect(
        tabBarBox,
        paints
          // Tab indicator
          ..line(
            color: theme.indicatorColor,
            strokeWidth: indicatorWeight,
            p1: const Offset(66.0, indicatorY),
            p2: const Offset(134.0, indicatorY),
          ),
      );
    });

    testWidgets('TabBar respects TabBarTheme.tabAlignment', (WidgetTester tester) async {
      final TabController controller = TabController(
        length: 2,
        vsync: const TestVSync(),
      );
      addTearDown(controller.dispose);

      // Test non-scrollable tab bar.
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(
            tabBarTheme: const TabBarTheme(tabAlignment: TabAlignment.center),
            useMaterial3: false,
          ),
          home: Scaffold(
            appBar: AppBar(
              bottom: TabBar(
                controller: controller,
                tabs: const <Widget>[
                  Tab(text: 'Tab 1'),
                  Tab(text: 'Tab 3'),
                ],
              ),
            ),
          ),
        ),
      );

      final Rect tabOneRect = tester.getRect(find.byType(Tab).first);
      final Rect tabTwoRect = tester.getRect(find.byType(Tab).last);

      final double tabOneLeft = (800 / 2) - tabOneRect.width - kTabLabelPadding.left;
      expect(tabOneRect.left, equals(tabOneLeft));
      final double tabTwoRight = (800 / 2) + tabTwoRect.width + kTabLabelPadding.right;
      expect(tabTwoRect.right, equals(tabTwoRight));
    });

    testWidgets('TabBar.tabAlignment overrides TabBarTheme.tabAlignment', (WidgetTester tester) async {
      final TabController controller = TabController(
        length: 2,
        vsync: const TestVSync(),
      );
      addTearDown(controller.dispose);

      // Test non-scrollable tab bar.
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(
            tabBarTheme: const TabBarTheme(tabAlignment: TabAlignment.fill),
            useMaterial3: false,
          ),
          home: Scaffold(
            appBar: AppBar(
              bottom: TabBar(
                tabAlignment: TabAlignment.center,
                controller: controller,
                tabs: const <Widget>[
                  Tab(text: 'Tab 1'),
                  Tab(text: 'Tab 3'),
                ],
              ),
            ),
          ),
        ),
      );

      final Rect tabOneRect = tester.getRect(find.byType(Tab).first);
      final Rect tabTwoRect = tester.getRect(find.byType(Tab).last);

      final double tabOneLeft = (800 / 2) - tabOneRect.width - kTabLabelPadding.left;
      expect(tabOneRect.left, equals(tabOneLeft));
      final double tabTwoRight = (800 / 2) + tabTwoRect.width + kTabLabelPadding.right;
      expect(tabTwoRect.right, equals(tabTwoRight));
    });
  });

  testWidgets('Material3 - TabBar indicator respects TabBarTheme.indicatorColor', (WidgetTester tester) async {
    final List<Widget> tabs = List<Widget>.generate(4, (int index) {
      return Tab(text: 'Tab $index');
    });

    final TabController controller = TabController(
      vsync: const TestVSync(),
      length: tabs.length,
    );
    addTearDown(controller.dispose);

    const Color tabBarThemeIndicatorColor = Color(0xffff0000);

    Widget buildTabBar({ required ThemeData theme }) {
      return MaterialApp(
        theme: theme,
        home: Material(
          child: Container(
            alignment: Alignment.topLeft,
            child: TabBar(
              controller: controller,
              tabs: tabs,
            ),
          ),
        ),
      );
    }

    await tester.pumpWidget(buildTabBar(theme: ThemeData(useMaterial3: true)));

    RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
    expect(tabBarBox,paints..rrect(color: ThemeData(useMaterial3: true).colorScheme.primary));

    await tester.pumpWidget(buildTabBar(theme: ThemeData(
      useMaterial3: true,
      tabBarTheme: const TabBarTheme(indicatorColor: tabBarThemeIndicatorColor)
    )));
    await tester.pumpAndSettle();

    tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
    expect(tabBarBox,paints..rrect(color: tabBarThemeIndicatorColor));
  });

  testWidgets('Material2 - TabBar indicator respects TabBarTheme.indicatorColor', (WidgetTester tester) async {
    final List<Widget> tabs = List<Widget>.generate(4, (int index) {
      return Tab(text: 'Tab $index');
    });

    final TabController controller = TabController(
      vsync: const TestVSync(),
      length: tabs.length,
    );
    addTearDown(controller.dispose);

    const Color themeIndicatorColor = Color(0xffff0000);
    const Color tabBarThemeIndicatorColor = Color(0xffffff00);

    Widget buildTabBar({ Color? themeIndicatorColor, Color? tabBarThemeIndicatorColor }) {
      return MaterialApp(
        theme: ThemeData(
          indicatorColor: themeIndicatorColor,
          tabBarTheme: TabBarTheme(indicatorColor: tabBarThemeIndicatorColor),
          useMaterial3: false,
        ),
        home: Material(
          child: Container(
            alignment: Alignment.topLeft,
            child: TabBar(
              controller: controller,
              tabs: tabs,
            ),
          ),
        ),
      );
    }

    await tester.pumpWidget(buildTabBar(themeIndicatorColor: themeIndicatorColor));

    RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
    expect(tabBarBox,paints..line(color: themeIndicatorColor));

    await tester.pumpWidget(buildTabBar(tabBarThemeIndicatorColor: tabBarThemeIndicatorColor));
    await tester.pumpAndSettle();

    tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
    expect(tabBarBox,paints..line(color: tabBarThemeIndicatorColor));
  });

   testWidgets('TabBarTheme.labelColor resolves material states', (WidgetTester tester) async {
    const Color selectedColor = Color(0xff00ff00);
    const Color unselectedColor = Color(0xffff0000);
    final MaterialStateColor labelColor = MaterialStateColor.resolveWith((Set<MaterialState> states) {
      if (states.contains(MaterialState.selected)) {
        return selectedColor;
      }
      return unselectedColor;
    });

    final TabBarTheme tabBarTheme = TabBarTheme(labelColor: labelColor);

    // Test labelColor correctly resolves material states.
    await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));

    final IconThemeData selectedTabIcon = IconTheme.of(tester.element(find.text(_tab1Text)));
    final IconThemeData unselectedTabIcon = IconTheme.of(tester.element(find.text(_tab2Text)));
    final TextStyle selectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab1Text)).text.style!;
    final TextStyle unselectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab2Text)).text.style!;

    expect(selectedTabIcon.color, selectedColor);
    expect(unselectedTabIcon.color, unselectedColor);
    expect(selectedTextStyle.color, selectedColor);
    expect(unselectedTextStyle.color, unselectedColor);
  });

  testWidgets('TabBarTheme.labelColor & TabBarTheme.unselectedLabelColor override material state TabBarTheme.labelColor',
    (WidgetTester tester) async {
      const Color selectedStateColor = Color(0xff00ff00);
      const Color unselectedStateColor = Color(0xffff0000);
      final MaterialStateColor labelColor = MaterialStateColor.resolveWith((Set<MaterialState> states) {
        if (states.contains(MaterialState.selected)) {
          return selectedStateColor;
        }
        return unselectedStateColor;
      });
      const Color selectedColor = Color(0xff00ffff);
      const Color unselectedColor = Color(0xffff12ff);

      TabBarTheme tabBarTheme = TabBarTheme(labelColor: labelColor);

      // Test material state label color.
      await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));

      IconThemeData selectedTabIcon = IconTheme.of(tester.element(find.text(_tab1Text)));
      IconThemeData unselectedTabIcon = IconTheme.of(tester.element(find.text(_tab2Text)));
      TextStyle selectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab1Text)).text.style!;
      TextStyle unselectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab2Text)).text.style!;

      expect(selectedTabIcon.color, selectedStateColor);
      expect(unselectedTabIcon.color, unselectedStateColor);
      expect(selectedTextStyle.color, selectedStateColor);
      expect(unselectedTextStyle.color, unselectedStateColor);

      // Test labelColor & unselectedLabelColor override material state labelColor.
      tabBarTheme = const TabBarTheme(
        labelColor: selectedColor,
        unselectedLabelColor: unselectedColor,
      );
      await tester.pumpWidget(buildTabBar(tabBarTheme: tabBarTheme));
      await tester.pumpAndSettle();

      selectedTabIcon = IconTheme.of(tester.element(find.text(_tab1Text)));
      unselectedTabIcon = IconTheme.of(tester.element(find.text(_tab2Text)));
      selectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab1Text)).text.style!;
      unselectedTextStyle = tester.renderObject<RenderParagraph>(find.text(_tab2Text)).text.style!;

      expect(selectedTabIcon.color, selectedColor);
      expect(unselectedTabIcon.color, unselectedColor);
      expect(selectedTextStyle.color, selectedColor);
      expect(unselectedTextStyle.color, unselectedColor);
  });
}