Skip to content

Instantly share code, notes, and snippets.

@StanislawNagorski
Last active November 14, 2022 20:40
Show Gist options
  • Save StanislawNagorski/403eabccdc5529af3159caff881fdb4b to your computer and use it in GitHub Desktop.
Save StanislawNagorski/403eabccdc5529af3159caff881fdb4b to your computer and use it in GitHub Desktop.
Button with 3 states: ON, OFF, DISABLE
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import '../presentation/values/values.dart';
class TextToggleOnOffButton extends HookWidget {
const TextToggleOnOffButton({
required this.buttonContent,
required this.onToggleOn,
required this.backgroundColor,
this.onToggleOff,
this.backgroundColorOnToggleOn,
this.buttonContentOnToggleOn,
this.enableToggleOnOff = false,
super.key,
}) : assert(
(enableToggleOnOff && backgroundColorOnToggleOn != null) || (enableToggleOnOff == false),
'If you are enabling toggle On/Off plz provide backgroundColorOnTap',
),
assert(
(enableToggleOnOff && onToggleOff != null) || (enableToggleOnOff == false),
'If you are enabling toggle On/Off plz provide onToggleOff function',
);
final Widget buttonContent;
final Widget? buttonContentOnToggleOn;
final Function onToggleOn;
final Function? onToggleOff;
final bool enableToggleOnOff;
final Color backgroundColor;
final Color? backgroundColorOnToggleOn;
@override
Widget build(BuildContext context) {
final buttonBackgroundColor = useState(backgroundColor);
final isPressed = useState(false);
final child = useState(buttonContent);
void changeButtonContentToToggleOn() {
if (buttonContentOnToggleOn != null) {
child.value = buttonContentOnToggleOn!;
}
}
void changeButtonContentToToggleOff() {
child.value = buttonContent;
}
void runToggleOff() {
buttonBackgroundColor.value = backgroundColor;
isPressed.value = false;
changeButtonContentToToggleOff();
onToggleOff!();
}
void runToggleOn() {
buttonBackgroundColor.value = backgroundColorOnToggleOn!;
isPressed.value = true;
changeButtonContentToToggleOn();
onToggleOn();
}
void runOnOffAction() {
final isButtonPressed = isPressed.value;
isButtonPressed ? runToggleOff() : runToggleOn();
}
void runOnOffActionOrOnTap() {
enableToggleOnOff ? runOnOffAction() : onToggleOn();
}
return GestureDetector(
key: UniqueKey(),
onTap: () => runOnOffActionOrOnTap(),
child: Container(
height: Dim.d50,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(AppDimensions.xxRoundedBorderRadius),
border: Border.all(
color: backgroundColorOnToggleOn ?? backgroundColor,
width: Dim.d2,
),
color: buttonBackgroundColor.value,
),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: Dim.d10, horizontal: Dim.d15),
child: Center(child: child.value),
),
),
);
}
}
//TESTS
const String _testText = 'Hello tests';
const String _testOnText = 'Bye bye tests';
const Color _testBackgroundColor = Colors.white;
const Color _testOnTapBackgroundColor = Colors.black;
void main() {
Widget testWidget({Function? onTestTap}) => MaterialApp(
home: Scaffold(
body: RepaintBoundary(
child: TextToggleOnOffButton(
buttonContent: const Text(_testText),
onToggleOn: onTestTap ?? () => print('Test button tapped'),
backgroundColor: _testBackgroundColor,
),
),
),
);
Widget testWidgetWithoutOnTapOffParam({Function? onTestTap}) => MaterialApp(
home: Scaffold(
body: RepaintBoundary(
child: TextToggleOnOffButton(
buttonContent: const Text(_testText),
onToggleOn: onTestTap ?? () => print('Test button tapped'),
backgroundColor: _testBackgroundColor,
backgroundColorOnToggleOn: _testOnTapBackgroundColor,
enableToggleOnOff: true,
),
),
),
);
Widget testWidgetWithoutBackgroundColorOnTapParam({Function? onTestTap}) => MaterialApp(
home: Scaffold(
body: RepaintBoundary(
child: TextToggleOnOffButton(
buttonContent: const Text(_testText),
onToggleOn: onTestTap ?? () => print('Test button tapped'),
backgroundColor: _testBackgroundColor,
enableToggleOnOff: true,
onToggleOff: () {},
),
),
),
);
Widget testWidgetWithFullToggleOnOffParamsSet({
Function? onTestToggleOn,
Function? onTestToggleOff,
bool? enableToggleOnOff,
}) =>
MaterialApp(
home: Scaffold(
body: RepaintBoundary(
child: TextToggleOnOffButton(
buttonContent: const Text(_testText),
buttonContentOnToggleOn: const Text(_testOnText),
onToggleOn: onTestToggleOn ?? () => print('Test button ON'),
onToggleOff: onTestToggleOff ?? () => print('Test button OFF'),
enableToggleOnOff: enableToggleOnOff ?? true,
backgroundColor: _testBackgroundColor,
backgroundColorOnToggleOn: _testOnTapBackgroundColor,
),
),
),
);
group('Basic rendering', () {
testWidgets(
'Should render text',
(tester) async {
//Given
await tester.pumpWidget(testWidget());
//Then
expect(find.text(_testText), findsOneWidget);
},
);
testWidgets(
'Should render proper background color',
(tester) async {
//Given
await tester.pumpWidget(testWidget());
final element = tester.element(find.byType(Container));
final container = element.widget as Container;
final resultDecorationColor = (container.decoration as BoxDecoration).color;
//Then
expect(resultDecorationColor, _testBackgroundColor);
},
);
});
group('Assertions', () {
testWidgets(
'Should render with all params',
(tester) async {
//Given
await tester.pumpWidget(testWidgetWithFullToggleOnOffParamsSet());
//Then
expect(find.text(_testText), findsOneWidget);
},
);
testWidgets(
'Should throw exception when enableToggleOnOff and backgroundColorOnToggleOn is not provided ',
(tester) async {
expect(
//When
() async => await tester.pumpWidget(testWidgetWithoutBackgroundColorOnTapParam()),
//Then
throwsAssertionError,
);
},
);
testWidgets(
'Should throw exception when enableToggleOnOff and onTapOff function is not provided ',
(tester) async {
expect(
//When
() async => await tester.pumpWidget(testWidgetWithoutOnTapOffParam()),
//Then
throwsAssertionError,
);
},
);
});
group('Background ON OFF state changes', () {
testWidgets(
'Should change background color after tap, toggle on',
(tester) async {
//Given
await tester.pumpWidget(testWidgetWithFullToggleOnOffParamsSet());
//When
await tester.tap(find.byType(TextToggleOnOffButton));
await tester.pump();
//Then
final element = tester.element(find.byType(Container));
final container = element.widget as Container;
final resultDecorationColor = (container.decoration as BoxDecoration).color;
expect(resultDecorationColor, _testOnTapBackgroundColor);
},
);
testWidgets(
'Should change background color after tap, and go back to original after another tap',
(tester) async {
//Given
await tester.pumpWidget(testWidgetWithFullToggleOnOffParamsSet());
//When
//Should change to _testOnTapBackgroundColor
await tester.tap(find.byType(TextToggleOnOffButton));
//Should change back to _testBackgroundColor
await tester.tap(find.byType(TextToggleOnOffButton));
await tester.pump();
//Then
final element = tester.element(find.byType(Container));
final container = element.widget as Container;
final resultDecorationColor = (container.decoration as BoxDecoration).color;
expect(resultDecorationColor, _testBackgroundColor);
},
);
});
group('Functions on and off', () {
testWidgets(
'Should run function on tap increasing counter to one',
(tester) async {
//Given
var testCounter = 0;
await tester.pumpWidget(testWidget(onTestTap: () => testCounter++));
//When
await tester.tap(find.byType(TextToggleOnOffButton));
//Then
expect(testCounter, 1);
},
);
testWidgets(
'Should NOT run onTestToggleOff function while enableToggleOnOff option is false',
(tester) async {
//Given
var testCounter = 0;
await tester.pumpWidget(
testWidgetWithFullToggleOnOffParamsSet(
enableToggleOnOff: false,
onTestToggleOff: () => testCounter++,
),
);
//When
//First tap run onToggleOn
await tester.tap(find.byType(TextToggleOnOffButton));
//Second tap run onToggleOff
await tester.tap(find.byType(TextToggleOnOffButton));
//Then
expect(testCounter, 0);
},
);
testWidgets(
'Should run ON and OFF function, increasing counter by 1 and then by 100',
(tester) async {
//Given
var testCounter = 0;
await tester.pumpWidget(
testWidgetWithFullToggleOnOffParamsSet(
onTestToggleOn: () => testCounter++,
onTestToggleOff: () => testCounter = testCounter + 100,
),
);
//When
//First tap run onToggleOn
await tester.tap(find.byType(TextToggleOnOffButton));
//Second tap run onToggleOff
await tester.tap(find.byType(TextToggleOnOffButton));
//Then
expect(testCounter, 101);
},
);
});
group('Content child ON OFF state changes', () {
testWidgets(
'Should change content after tap, toggle on',
(tester) async {
//Given
await tester.pumpWidget(testWidgetWithFullToggleOnOffParamsSet());
//When
await tester.tap(find.byType(TextToggleOnOffButton));
await tester.pump();
//Then
expect(find.text(_testOnText), findsOneWidget);
},
);
testWidgets(
'Should change content after tap, and go back to original after another tap',
(tester) async {
//Given
await tester.pumpWidget(testWidgetWithFullToggleOnOffParamsSet());
//When
//Should change to _testOnText
await tester.tap(find.byType(TextToggleOnOffButton));
//Should change back to _testText
await tester.tap(find.byType(TextToggleOnOffButton));
await tester.pump();
//Then
expect(find.text(_testText), findsOneWidget);
},
);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment