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

Code before:
          controller: tabBarController,
          scrollController: tabScrollController,
          tabs: tabs,
        ),
      ),
    );

    // Creating the new scroll position should not throw.
    expect(tester.takeException(), isNull);
    final ScrollableState scrollableState = tester.state<ScrollableState>(find.byType(Scrollable));

    // The pixels of the new position should be the same as before the update.
    expect(scrollableState.position.pixels, oldPixels);

    // This should not throw, since the tab bar is attached to the external controller.
    expect(tabScrollController.debugCheckHasTabBarState(), isTrue);
    expect(tester.takeException(), isNull);
  });

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

    final TabBarScrollController controllerA = TabBarScrollController();
    final TabBarScrollController controllerB = TabBarScrollController();
    addTearDown(controllerA.dispose);
    addTearDown(controllerB.dispose);

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

    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,

Review comment (suggestion):
```suggestion
    final controllerA = TabBarScrollController();
    final controllerB = TabBarScrollController();
```

Code after:
          controller: tabBarController,
          scrollController: tabScrollController,
          tabs: tabs,
        ),
      ),
    );

    // Creating the new scroll position should not throw.
    expect(tester.takeException(), isNull);
    final ScrollableState scrollableState = tester.state<ScrollableState>(find.byType(Scrollable));

    // The pixels of the new position should be the same as before the update.
    expect(scrollableState.position.pixels, oldPixels);

    // This should not throw, since the tab bar is attached to the external controller.
    expect(tabScrollController.debugCheckHasTabBarState(), isTrue);
    expect(tester.takeException(), isNull);
  });

  testWidgets(
    'TabBar correctly detaches old external TabBarScrollController when switched to a new one',
    (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 controllerA = TabBarScrollController();
      final controllerB = TabBarScrollController();
      addTearDown(controllerA.dispose);
      addTearDown(controllerB.dispose);

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

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

      // Switch to controllerB
      await tester.pumpWidget(
        boilerplate(
          child: TabBar(
            isScrollable: true,