// 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/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { RenderObject getOverlayColor(WidgetTester tester) { return tester.allRenderObjects.firstWhere( (RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures', ); } test('SegmentedButtonThemeData copyWith, ==, hashCode basics', () { expect(const SegmentedButtonThemeData(), const SegmentedButtonThemeData().copyWith()); expect( const SegmentedButtonThemeData().hashCode, const SegmentedButtonThemeData().copyWith().hashCode, ); const custom = SegmentedButtonThemeData( style: ButtonStyle(backgroundColor: MaterialStatePropertyAll(Colors.green)), selectedIcon: Icon(Icons.error), ); final SegmentedButtonThemeData copy = const SegmentedButtonThemeData().copyWith( style: custom.style, selectedIcon: custom.selectedIcon, ); expect(copy, custom); }); test('SegmentedButtonThemeData lerp special cases', () { expect(SegmentedButtonThemeData.lerp(null, null, 0), const SegmentedButtonThemeData()); const theme = SegmentedButtonThemeData(); expect(identical(SegmentedButtonThemeData.lerp(theme, theme, 0.5), theme), true); }); testWidgets('Default SegmentedButtonThemeData debugFillProperties', (WidgetTester tester) async { final builder = DiagnosticPropertiesBuilder(); const SegmentedButtonThemeData().debugFillProperties(builder); final List description = builder.properties .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) .map((DiagnosticsNode node) => node.toString()) .toList(); expect(description, []); }); testWidgets('With no other configuration, defaults are used', (WidgetTester tester) async { final theme = ThemeData(); await tester.pumpWidget( MaterialApp( theme: theme, home: Scaffold( body: Center( child: SegmentedButton( segments: const >[ ButtonSegment(value: 1, label: Text('1')), ButtonSegment(value: 2, label: Text('2')), ButtonSegment(value: 3, label: Text('3'), enabled: false), ], selected: const {2}, onSelectionChanged: (Set selected) {}, ), ), ), ), ); // Test first segment, should be enabled { final Finder text = find.text('1'); final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first; final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.check)); final Material material = tester.widget(parent); expect(material.color, Colors.transparent); expect(material.shape, const RoundedRectangleBorder()); expect(material.textStyle!.color, theme.colorScheme.onSurface); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); expect(material.textStyle!.fontWeight, FontWeight.w500); expect(selectedIcon, findsNothing); } // Test second segment, should be enabled and selected { final Finder text = find.text('2'); final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first; final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.check)); final Material material = tester.widget(parent); expect(material.color, theme.colorScheme.secondaryContainer); expect(material.shape, const RoundedRectangleBorder()); expect(material.textStyle!.color, theme.colorScheme.onSecondaryContainer); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); expect(material.textStyle!.fontWeight, FontWeight.w500); expect(selectedIcon, findsOneWidget); } // Test last segment, should be disabled { final Finder text = find.text('3'); final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first; final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.check)); final Material material = tester.widget(parent); expect(material.color, Colors.transparent); expect(material.shape, const RoundedRectangleBorder()); expect(material.textStyle!.color, theme.colorScheme.onSurface.withOpacity(0.38)); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); expect(material.textStyle!.fontWeight, FontWeight.w500); expect(selectedIcon, findsNothing); } }); testWidgets('ThemeData.segmentedButtonTheme overrides defaults', (WidgetTester tester) async { final theme = ThemeData( segmentedButtonTheme: SegmentedButtonThemeData( style: ButtonStyle( backgroundColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.disabled)) { return Colors.blue; } if (states.contains(WidgetState.selected)) { return Colors.purple; } return null; }), foregroundColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.disabled)) { return Colors.yellow; } if (states.contains(WidgetState.selected)) { return Colors.brown; } else { return Colors.cyan; } }), ), selectedIcon: const Icon(Icons.error), ), ); await tester.pumpWidget( MaterialApp( theme: theme, home: Scaffold( body: Center( child: SegmentedButton( segments: const >[ ButtonSegment(value: 1, label: Text('1')), ButtonSegment(value: 2, label: Text('2')), ButtonSegment(value: 3, label: Text('3'), enabled: false), ], selected: const {2}, onSelectionChanged: (Set selected) {}, ), ), ), ), ); // Test first segment, should be enabled { final Finder text = find.text('1'); final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first; final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.error)); final Material material = tester.widget(parent); expect(material.color, Colors.transparent); expect(material.shape, const RoundedRectangleBorder()); expect(material.textStyle!.color, Colors.cyan); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); expect(material.textStyle!.fontWeight, FontWeight.w500); expect(selectedIcon, findsNothing); } // Test second segment, should be enabled and selected { final Finder text = find.text('2'); final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first; final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.error)); final Material material = tester.widget(parent); expect(material.color, Colors.purple); expect(material.shape, const RoundedRectangleBorder()); expect(material.textStyle!.color, Colors.brown); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); expect(material.textStyle!.fontWeight, FontWeight.w500); expect(selectedIcon, findsOneWidget); } // Test last segment, should be disabled { final Finder text = find.text('3'); final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first; final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.error)); final Material material = tester.widget(parent); expect(material.color, Colors.blue); expect(material.shape, const RoundedRectangleBorder()); expect(material.textStyle!.color, Colors.yellow); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); expect(material.textStyle!.fontWeight, FontWeight.w500); expect(selectedIcon, findsNothing); } }); testWidgets('SegmentedButtonTheme overrides ThemeData and defaults', (WidgetTester tester) async { final global = SegmentedButtonThemeData( style: ButtonStyle( backgroundColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.disabled)) { return Colors.blue; } if (states.contains(WidgetState.selected)) { return Colors.purple; } return null; }), foregroundColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.disabled)) { return Colors.yellow; } if (states.contains(WidgetState.selected)) { return Colors.brown; } else { return Colors.cyan; } }), ), selectedIcon: const Icon(Icons.error), ); final segmentedTheme = SegmentedButtonThemeData( style: ButtonStyle( backgroundColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.disabled)) { return Colors.lightBlue; } if (states.contains(WidgetState.selected)) { return Colors.lightGreen; } return null; }), foregroundColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.disabled)) { return Colors.lime; } if (states.contains(WidgetState.selected)) { return Colors.amber; } else { return Colors.deepPurple; } }), ), selectedIcon: const Icon(Icons.plus_one), ); final theme = ThemeData(segmentedButtonTheme: global); await tester.pumpWidget( MaterialApp( theme: theme, home: SegmentedButtonTheme( data: segmentedTheme, child: Scaffold( body: Center( child: SegmentedButton( segments: const >[ ButtonSegment(value: 1, label: Text('1')), ButtonSegment(value: 2, label: Text('2')), ButtonSegment(value: 3, label: Text('3'), enabled: false), ], selected: const {2}, onSelectionChanged: (Set selected) {}, ), ), ), ), ), ); // Test first segment, should be enabled { final Finder text = find.text('1'); final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first; final Finder selectedIcon = find.descendant( of: parent, matching: find.byIcon(Icons.plus_one), ); final Material material = tester.widget(parent); expect(material.animationDuration, const Duration(milliseconds: 200)); expect(material.borderRadius, null); expect(material.color, Colors.transparent); expect(material.shape, const RoundedRectangleBorder()); expect(material.textStyle!.color, Colors.deepPurple); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); expect(material.textStyle!.fontWeight, FontWeight.w500); expect(selectedIcon, findsNothing); } // Test second segment, should be enabled and selected { final Finder text = find.text('2'); final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first; final Finder selectedIcon = find.descendant( of: parent, matching: find.byIcon(Icons.plus_one), ); final Material material = tester.widget(parent); expect(material.animationDuration, const Duration(milliseconds: 200)); expect(material.borderRadius, null); expect(material.color, Colors.lightGreen); expect(material.shape, const RoundedRectangleBorder()); expect(material.textStyle!.color, Colors.amber); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); expect(material.textStyle!.fontWeight, FontWeight.w500); expect(selectedIcon, findsOneWidget); } // Test last segment, should be disabled { final Finder text = find.text('3'); final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first; final Finder selectedIcon = find.descendant( of: parent, matching: find.byIcon(Icons.plus_one), ); final Material material = tester.widget(parent); expect(material.animationDuration, const Duration(milliseconds: 200)); expect(material.borderRadius, null); expect(material.color, Colors.lightBlue); expect(material.shape, const RoundedRectangleBorder()); expect(material.textStyle!.color, Colors.lime); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); expect(material.textStyle!.fontWeight, FontWeight.w500); expect(selectedIcon, findsNothing); } }); testWidgets('Widget parameters overrides SegmentedTheme, ThemeData and defaults', ( WidgetTester tester, ) async { final global = SegmentedButtonThemeData( style: ButtonStyle( backgroundColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.disabled)) { return Colors.blue; } if (states.contains(WidgetState.selected)) { return Colors.purple; } return null; }), foregroundColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.disabled)) { return Colors.yellow; } if (states.contains(WidgetState.selected)) { return Colors.brown; } else { return Colors.cyan; } }), ), selectedIcon: const Icon(Icons.error), ); final segmentedTheme = SegmentedButtonThemeData( style: ButtonStyle( backgroundColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.disabled)) { return Colors.lightBlue; } if (states.contains(WidgetState.selected)) { return Colors.lightGreen; } return null; }), foregroundColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.disabled)) { return Colors.lime; } if (states.contains(WidgetState.selected)) { return Colors.amber; } else { return Colors.deepPurple; } }), ), selectedIcon: const Icon(Icons.plus_one), ); final theme = ThemeData(segmentedButtonTheme: global); await tester.pumpWidget( MaterialApp( theme: theme, home: SegmentedButtonTheme( data: segmentedTheme, child: Scaffold( body: Center( child: SegmentedButton( segments: const >[ ButtonSegment(value: 1, label: Text('1')), ButtonSegment(value: 2, label: Text('2')), ButtonSegment(value: 3, label: Text('3'), enabled: false), ], selected: const {2}, onSelectionChanged: (Set selected) {}, style: ButtonStyle( backgroundColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.disabled)) { return Colors.black12; } if (states.contains(WidgetState.selected)) { return Colors.grey; } return null; }), foregroundColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.disabled)) { return Colors.amberAccent; } if (states.contains(WidgetState.selected)) { return Colors.deepOrange; } else { return Colors.deepPurpleAccent; } }), ), selectedIcon: const Icon(Icons.alarm), ), ), ), ), ), ); // Test first segment, should be enabled { final Finder text = find.text('1'); final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first; final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.alarm)); final Material material = tester.widget(parent); expect(material.animationDuration, const Duration(milliseconds: 200)); expect(material.borderRadius, null); expect(material.color, Colors.transparent); expect(material.shape, const RoundedRectangleBorder()); expect(material.textStyle!.color, Colors.deepPurpleAccent); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); expect(material.textStyle!.fontWeight, FontWeight.w500); expect(selectedIcon, findsNothing); } // Test second segment, should be enabled and selected { final Finder text = find.text('2'); final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first; final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.alarm)); final Material material = tester.widget(parent); expect(material.animationDuration, const Duration(milliseconds: 200)); expect(material.borderRadius, null); expect(material.color, Colors.grey); expect(material.shape, const RoundedRectangleBorder()); expect(material.textStyle!.color, Colors.deepOrange); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); expect(material.textStyle!.fontWeight, FontWeight.w500); expect(selectedIcon, findsOneWidget); } // Test last segment, should be disabled { final Finder text = find.text('3'); final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first; final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.alarm)); final Material material = tester.widget(parent); expect(material.animationDuration, const Duration(milliseconds: 200)); expect(material.borderRadius, null); expect(material.color, Colors.black12); expect(material.shape, const RoundedRectangleBorder()); expect(material.textStyle!.color, Colors.amberAccent); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); expect(material.textStyle!.fontWeight, FontWeight.w500); expect(selectedIcon, findsNothing); } }); testWidgets( 'SegmentedButtonTheme SegmentedButton.styleFrom overlayColor overrides default overlay color', (WidgetTester tester) async { const overlayColor = Color(0xffff0000); await tester.pumpWidget( MaterialApp( theme: ThemeData( segmentedButtonTheme: SegmentedButtonThemeData( style: SegmentedButton.styleFrom(overlayColor: overlayColor), ), ), home: Scaffold( body: Center( child: SegmentedButton( segments: const >[ ButtonSegment(value: 0, label: Text('Option 1')), ButtonSegment(value: 1, label: Text('Option 2')), ], onSelectionChanged: (Set selected) {}, selected: const {1}, ), ), ), ), ); // Hovered selected segment, Offset center = tester.getCenter(find.text('Option 1')); final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); await gesture.addPointer(); await gesture.moveTo(center); await tester.pumpAndSettle(); expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08))); // Hovered unselected segment, center = tester.getCenter(find.text('Option 2')); await gesture.moveTo(center); await tester.pumpAndSettle(); expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08))); // Highlighted unselected segment (pressed). center = tester.getCenter(find.text('Option 1')); await gesture.down(center); await tester.pumpAndSettle(); expect( getOverlayColor(tester), paints ..rect(color: overlayColor.withOpacity(0.08)) ..rect(color: overlayColor.withOpacity(0.1)), ); // Remove pressed and hovered states, await gesture.up(); await tester.pumpAndSettle(); await gesture.moveTo(const Offset(0, 50)); await tester.pumpAndSettle(); // Highlighted selected segment (pressed) center = tester.getCenter(find.text('Option 2')); await gesture.down(center); await tester.pumpAndSettle(); expect( getOverlayColor(tester), paints ..rect(color: overlayColor.withOpacity(0.08)) ..rect(color: overlayColor.withOpacity(0.1)), ); // Remove pressed and hovered states, await gesture.up(); await tester.pumpAndSettle(); await gesture.moveTo(const Offset(0, 50)); await tester.pumpAndSettle(); // Focused unselected segment. await tester.sendKeyEvent(LogicalKeyboardKey.tab); await tester.pumpAndSettle(); expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.1))); // Focused selected segment. await tester.sendKeyEvent(LogicalKeyboardKey.tab); await tester.pumpAndSettle(); expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.1))); }, ); }