From 4d93a5e1f85434d48cc6371c7246021233cddc8e Mon Sep 17 00:00:00 2001 From: TAJAOUART Mounir Date: Sat, 11 Feb 2023 10:20:11 +0100 Subject: [PATCH] added widget tests --- example/test/widget_test.dart | 19 +-- lib/real_time_chart.dart | 4 +- lib/src/chart_display.dart | 19 +++ lib/src/live_chart.dart | 58 ++++++--- lib/src/point.dart | 20 +++ test/real_time_chart_test.dart | 219 ++++++++++++++++++++++++++++++++- 6 files changed, 295 insertions(+), 44 deletions(-) diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index 092d222..c9578a1 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -10,21 +10,4 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:example/main.dart'; -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} +void main() {} diff --git a/lib/real_time_chart.dart b/lib/real_time_chart.dart index fe10296..d90a910 100644 --- a/lib/real_time_chart.dart +++ b/lib/real_time_chart.dart @@ -1,5 +1,3 @@ -library real_time_chart; - export 'src/chart_display.dart'; export 'src/point.dart'; -export 'src/live_chart.dart'; \ No newline at end of file +export 'src/live_chart.dart'; diff --git a/lib/src/chart_display.dart b/lib/src/chart_display.dart index d00db09..60b1c42 100644 --- a/lib/src/chart_display.dart +++ b/lib/src/chart_display.dart @@ -1 +1,20 @@ +/* + * Copyright (C) 2023 tajaouart.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contact: developer@tajaouart.com + */ + enum ChartDisplay { points, line } diff --git a/lib/src/live_chart.dart b/lib/src/live_chart.dart index 1dafa53..f2341e7 100644 --- a/lib/src/live_chart.dart +++ b/lib/src/live_chart.dart @@ -1,3 +1,23 @@ +/* + * Copyright (C) 2023 tajaouart.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contact: developer@tajaouart.com + */ + + import 'dart:async'; import 'dart:math'; @@ -7,7 +27,9 @@ import 'chart_display.dart'; import 'point.dart'; class RealTimeGraph extends StatefulWidget { - const RealTimeGraph({ + final GlobalKey globalKey = GlobalKey(); + + RealTimeGraph({ this.updateDelay = const Duration(milliseconds: 50), this.supportNegativeValuesDisplay = false, this.displayMode = ChartDisplay.line, @@ -24,7 +46,6 @@ class RealTimeGraph extends StatefulWidget { this.minValue = 0, this.speed = 1, Key? key, - }) : super(key: key); // Callback to build custom Y-axis text. @@ -83,7 +104,7 @@ class RealTimeGraphState extends State StreamSubscription? streamSubscription; // List of data points to be displayed on the graph - List> _data = []; + List> data = []; // Timer to periodically update the data for visualization Timer? timer; @@ -101,31 +122,31 @@ class RealTimeGraphState extends State // Start a periodic timer to update the data for visualization timer = Timer.periodic(widget.updateDelay, (_) { // delete data that is no longer displayed on the graph. - _data.removeWhere((element) => element.x < canvasWidth * -1.5); + data.removeWhere((element) => element.x < canvasWidth * -1.5); // Clone the data to avoid modifying the original list while iterating - List> data = _data.map((e) => e).toList(); + List> newData = data.map((e) => e).toList(); // Increment the x value of each data point - for (var element in data) { + for (var element in newData) { element.x = element.x - widget.speed; } // Trigger a rebuild with the updated data setState(() { - _data = data; + data = newData; }); }); } // Maximum value of the y-axis of the graph double get maxValue { - if (_data.isEmpty) { + if (data.isEmpty) { return 0; } - final maxValue = _data.map((point) => point.y).reduce(max); - final minValue = _data.map((point) => point.y).reduce(min); + final maxValue = data.map((point) => point.y).reduce(max); + final minValue = data.map((point) => point.y).reduce(min); if (widget.supportNegativeValuesDisplay) { if (maxValue > minValue.abs()) { @@ -140,12 +161,12 @@ class RealTimeGraphState extends State // Minimum value of the y-axis of the graph double get minValue { - if (_data.isEmpty) { + if (data.isEmpty) { return 0; } - final maxValue = _data.map((point) => point.y).reduce(max); - final minValue = _data.map((point) => point.y).reduce(min); + final maxValue = data.map((point) => point.y).reduce(max); + final minValue = data.map((point) => point.y).reduce(min); if (widget.supportNegativeValuesDisplay) { if (maxValue > minValue.abs()) { @@ -182,6 +203,7 @@ class RealTimeGraphState extends State // Display the y-axis line if (widget.displayYAxisLines) Container( + key: const Key('Y-Axis'), color: widget.yAxisColor, width: widget.axisStroke, height: double.maxFinite, @@ -207,13 +229,14 @@ class RealTimeGraphState extends State child: ClipRRect( child: RepaintBoundary( child: CustomPaint( + key: Key(widget.displayMode.toString()), size: Size( constraints.maxWidth, constraints.maxHeight, ), painter: widget.displayMode == ChartDisplay.points ? _PointGraphPainter( - data: _data, + data: data, pointsSpacing: widget.pointsSpacing, graphStroke: widget.graphStroke, color: widget.graphColor, @@ -221,7 +244,7 @@ class RealTimeGraphState extends State widget.supportNegativeValuesDisplay, ) : _LineGraphPainter( - data: _data, + data: data, graphStroke: widget.graphStroke, color: widget.graphColor, supportNegativeValuesDisplay: @@ -240,6 +263,7 @@ class RealTimeGraphState extends State ? Alignment.center : Alignment.bottomCenter, child: Container( + key: const Key('X-Axis'), color: widget.xAxisColor, height: widget.axisStroke, width: double.maxFinite, @@ -259,9 +283,9 @@ class RealTimeGraphState extends State ); } - void _streamListener(double data) { + void _streamListener(double value) { // Insert the new data point in the beginning of the list - _data.insert(0, Point(0, data)); + data.insert(0, Point(0, value)); } @override diff --git a/lib/src/point.dart b/lib/src/point.dart index 4dd4205..de22f6e 100644 --- a/lib/src/point.dart +++ b/lib/src/point.dart @@ -1,3 +1,23 @@ +/* + * Copyright (C) 2023 tajaouart.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contact: developer@tajaouart.com + */ + + class Point { T x; T y; diff --git a/test/real_time_chart_test.dart b/test/real_time_chart_test.dart index 32331b5..a9fc050 100644 --- a/test/real_time_chart_test.dart +++ b/test/real_time_chart_test.dart @@ -1,12 +1,219 @@ -import 'package:flutter_test/flutter_test.dart'; +/* + * Copyright (C) 2023 tajaouart.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contact: developer@tajaouart.com + */ + +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:real_time_chart/real_time_chart.dart'; void main() { - test('adds one to input values', () { - final calculator = Calculator(); - expect(calculator.addOne(2), 3); - expect(calculator.addOne(-7), -6); - expect(calculator.addOne(0), 1); + testWidgets('Check Stream behaviour & initial values', (tester) async { + final streamController = StreamController(); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: RealTimeGraph( + key: const Key('RealTimeGraph'), + stream: streamController.stream, + supportNegativeValuesDisplay: true, + ), + ), + ), + ); + + final state = tester.state(find.byType(RealTimeGraph)); + + await tester.pumpAndSettle(const Duration(seconds: 1)); + + // Verify that the `streamSubscription` is created and active + expect(find.byType(RealTimeGraph), findsOneWidget); + + expect(state.streamSubscription, isNotNull); + expect(state.streamSubscription!.isPaused, isFalse); + + // Verify that the timer is created and active + expect(state.timer, isNotNull); + expect(state.timer!.isActive, isTrue); + + // Verify that the data starts with an empty list + expect(state.data, isEmpty); + + // Add data to the stream and verify that the graph updates + streamController.add(10.0); + await tester.pump(); + expect(state.data, isNotEmpty); + expect(state.data[0].y, 10.0); + + // Verify that the `minValue` and `maxValue` functions return the correct values + streamController.add(-5.0); + await tester.pumpAndSettle(); + + // min & max values are always symetrics + expect(state.minValue, -10.0); + expect(state.maxValue, 10.0); + + streamController.close(); + }); + + testWidgets('displayYAxisValues > true', (WidgetTester tester) async { + // Create a StreamController to simulate the stream data + final streamController = StreamController(); + + // Build the widget tree + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: RealTimeGraph( + displayYAxisValues: true, + axisTextBuilder: (double value) { + return Text( + '$value', + style: const TextStyle(color: Colors.purple), + ); + }, + stream: streamController.stream, + ), + ), + ), + ); + + // Add data to the stream + streamController.add(1.0); + streamController.add(2.0); + + // Trigger a rebuild of the widget tree to reflect the changes + await tester.pumpAndSettle(); + + // Verify the y axis values after the stream update + expect(find.text('0.0'), findsOneWidget); + // median value. + expect(find.text('1.0'), findsOneWidget); + // max value + expect(find.text('2.0'), findsOneWidget); + + // Verify that the text builder is used + final text = tester.widget(find.text('0.0')); + expect(text.style?.color, Colors.purple); + }); + + testWidgets('displayYAxisValues > false', (WidgetTester tester) async { + // Create a StreamController to simulate the stream data + final StreamController streamController = + StreamController(); + + // Build the widget tree + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: RealTimeGraph( + displayYAxisValues: false, + axisTextBuilder: (double value) => Text('$value'), + stream: streamController.stream, + ), + ), + ), + ); + + // Add data to the stream + streamController.add(1.0); + streamController.add(2.0); + + // Trigger a rebuild of the widget tree to reflect the changes + await tester.pumpAndSettle(); + + // Verify the y axis values after the stream update + expect(find.text('0.0'), findsNothing); + // median value. + expect(find.text('1.0'), findsNothing); + // max value + expect(find.text('2.0'), findsNothing); + }); + + testWidgets('Axis stroke and color', (WidgetTester tester) async { + // Create a StreamController to simulate the stream data + final streamController = StreamController(); + + // Build the widget tree + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: RealTimeGraph( + displayYAxisValues: true, + xAxisColor: Colors.red, + yAxisColor: Colors.green, + axisStroke: 2.0, + stream: streamController.stream, + ), + ), + ), + ); + + // Test if the axis's colors + expect( + tester.widget(find.byKey(const Key('X-Axis')).first).color, + Colors.red, + ); + + expect( + tester.widget(find.byKey(const Key('Y-Axis')).first).color, + Colors.green, + ); + + // Test if the axis stroke is 2.0 + final size1 = tester.getSize(find.byKey(const Key('X-Axis'))); + final size2 = tester.getSize(find.byKey(const Key('Y-Axis'))); + expect(size1.height, equals(2.0)); + expect(size2.width, equals(2.0)); + }); + + testWidgets('Verify behaviour of displayMode property ', (tester) async { + // Create a StreamController to simulate the stream data + final streamController = StreamController(); + + // Build the widget tree + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: RealTimeGraph( + displayMode: ChartDisplay.line, + stream: streamController.stream, + ), + ), + ), + ); + + expect(find.byKey(Key(ChartDisplay.line.toString())), findsOneWidget); + + // Build the widget tree + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: RealTimeGraph( + displayMode: ChartDisplay.points, + stream: streamController.stream, + ), + ), + ), + ); + + expect(find.byKey(Key(ChartDisplay.points.toString())), findsOneWidget); }); }