Repository: flutter/flutter
File: packages/flutter/test/material/tabs_test.dart (Dart)

Code before:

    expect(controllerA.debugCheckHasTabBarState(), isTrue);
    expect(() => controllerB.debugCheckHasTabBarState(), throwsAssertionError);

    // Switch to controllerB
    await tester.pumpWidget(
      boilerplate(
        child: TabBar(
          isScrollable: true,
          controller: TabController(length: tabs.length, vsync: const TestVSync()),
          scrollController: controllerB,
          tabs: tabs,
        ),
      ),
    );

    expect(controllerB.debugCheckHasTabBarState(), isTrue);
    expect(() => controllerA.debugCheckHasTabBarState(), throwsAssertionError);
  });

  testWidgets('TabBar correctly detaches external TabBarScrollController when disposed', (WidgetTester tester) async {
    final List<Tab> tabs = <Tab>[
      for (int i = 0; i < 10; i++) Tab(text: 'Tab $i'),
    ];

    final TabBarScrollController controller = TabBarScrollController();
    addTearDown(controller.dispose);

    await tester.pumpWidget(
      boilerplate(
        child: TabBar(
          isScrollable: true,
          controller: TabController(length: tabs.length, vsync: const TestVSync()),
          scrollController: controller,
          tabs: tabs,
        ),
      ),
    );

    expect(controller.debugCheckHasTabBarState(), isTrue);

    // Dispose the TabBar by pumping a different widget
    await tester.pumpWidget(boilerplate(child: const SizedBox.shrink()));

    expect(() => controller.debugCheckHasTabBarState(), throwsAssertionError);
  });

  // Regression test for https://github.com/flutter/flutter/issues/124608
  testWidgets('TabBar can be wrapped with RawScrollbar', (WidgetTester tester) async {
    final tabs = List<Tab>.generate(6, (int index) {
      return Tab(text: 'TAB #$index');

Review comment (suggestion):
```suggestion
    final controller = TabBarScrollController();
```

Code after:
          ),
        ),
      );

      expect(controllerA.debugCheckHasTabBarState(), isTrue);
      expect(() => controllerB.debugCheckHasTabBarState(), throwsAssertionError);

      // Switch to controllerB
      await tester.pumpWidget(
        boilerplate(
          child: TabBar(
            isScrollable: true,
            controller: tabController,
            scrollController: controllerB,
            tabs: tabs,
          ),
        ),
      );

      expect(controllerB.debugCheckHasTabBarState(), isTrue);
      expect(() => controllerA.debugCheckHasTabBarState(), throwsAssertionError);
    },
  );

  testWidgets('TabBar correctly detaches external TabBarScrollController when disposed', (
    WidgetTester tester,
  ) async {
    final tabs = <Tab>[for (int i = 0; i < 10; i++) Tab(text: 'Tab $i')];

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

    await tester.pumpWidget(
      boilerplate(
        child: TabBar(
          isScrollable: true,
          controller: tabController,
          scrollController: controller,
          tabs: tabs,
        ),
      ),
    );

    expect(controller.debugCheckHasTabBarState(), isTrue);

    // Dispose the TabBar by pumping a different widget
    await tester.pumpWidget(boilerplate(child: const SizedBox.shrink()));

    expect(() => controller.debugCheckHasTabBarState(), throwsAssertionError);