Skip to content

Commit

Permalink
[RSDK-4265] Sensor widget tests (#81)
Browse files Browse the repository at this point in the history
  • Loading branch information
njooma authored Aug 2, 2023
1 parent c36161b commit 20344fc
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 31 deletions.
73 changes: 50 additions & 23 deletions lib/widgets/button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ enum ViamButtonRole {
/// A button to indicate a dangerous operation, will result in an red color scheme
danger;

/// The background color of the button, based on role. Takes dark mode into consideration.
Color get backgroundColor {
final brightness = SchedulerBinding.instance.platformDispatcher.platformBrightness;
final isDarkMode = brightness == Brightness.dark;
Expand All @@ -35,6 +36,7 @@ enum ViamButtonRole {
}
}

/// The foreground color of the button, based on role. Takes dark mode into consideration.
Color get foregroundColor {
final brightness = SchedulerBinding.instance.platformDispatcher.platformBrightness;
final isDarkMode = brightness == Brightness.dark;
Expand All @@ -52,6 +54,7 @@ enum ViamButtonRole {
}
}

/// The material color of the button, based on role.
MaterialColor get materialColor {
switch (this) {
case ViamButtonRole.primary:
Expand All @@ -66,24 +69,29 @@ enum ViamButtonRole {
return Colors.amber;
}
}

ButtonStyle get style =>
ButtonStyle(backgroundColor: MaterialStatePropertyAll(backgroundColor), foregroundColor: MaterialStatePropertyAll(foregroundColor));
}

/// The fill style of the button.
enum ViamButtonFillStyle {
/// The button should be filled entirely
filled,

/// The button be outlined
outline,

/// The button's background should be transparent
ghost;
}

/// The size class of the button.
enum ViamButtonSizeClass {
xs,
small,
medium,
large,
xl;

/// The font size of the button, based on size class
double get fontSize {
switch (this) {
case xs:
Expand All @@ -99,6 +107,7 @@ enum ViamButtonSizeClass {
}
}

/// The padding of the button, based on size class
EdgeInsets get padding {
switch (this) {
case xs:
Expand All @@ -114,24 +123,45 @@ enum ViamButtonSizeClass {
}
}

/// The style of the button, based on size class
ButtonStyle get style {
return ButtonStyle(textStyle: MaterialStatePropertyAll(TextStyle(fontSize: fontSize)), padding: MaterialStatePropertyAll(padding));
}
}

/// The variant of the button
enum ViamButtonVariant {
/// The button should only show the icon
iconOnly,

/// The icon should be ahead of the text
iconLeading,

/// The icon should be after the text
iconTrailing;
}

/// A button that has Viam specific styling
class ViamButton extends StatelessWidget {
/// The text of the button
final String text;

/// The action that should be performed when the button is pressed
final VoidCallback onPressed;

/// The icon to display
final IconData? icon;

/// The role of the button
final ViamButtonRole role;

/// The fill style of the button
final ViamButtonFillStyle style;

/// The button variant
final ViamButtonVariant variant;

/// The size class of the button
final ViamButtonSizeClass size;

const ViamButton(
Expand All @@ -153,7 +183,7 @@ class ViamButton extends StatelessWidget {
fgColor = role.foregroundColor;
}
return mainStyle.copyWith(
backgroundColor: const MaterialStatePropertyAll(Color.fromARGB(0, 0, 0, 0)),
backgroundColor: const MaterialStatePropertyAll(Colors.transparent),
foregroundColor: MaterialStatePropertyAll(fgColor),
);
}
Expand Down Expand Up @@ -181,26 +211,23 @@ class ViamButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
Widget child;
if (icon != null) {
final prePadding = (variant == ViamButtonVariant.iconOnly) ? const Text(' ') : const SizedBox.shrink();
final iconWidget = Row(children: [prePadding, Icon(icon!)]);
final label = (variant == ViamButtonVariant.iconOnly)
? const SizedBox.shrink()
: Text(
text,
style: const TextStyle(fontWeight: FontWeight.bold),
);
final first = (variant == ViamButtonVariant.iconTrailing) ? label : iconWidget;
final second = (variant == ViamButtonVariant.iconTrailing) ? iconWidget : label;
if (style == ViamButtonFillStyle.outline) {
child = OutlinedButton.icon(onPressed: onPressed, icon: first, label: second, style: _buttonStyle);
} else {
child = TextButton.icon(onPressed: onPressed, icon: first, label: second, style: _buttonStyle);
}
} else if (style == ViamButtonFillStyle.outline) {
child = OutlinedButton(onPressed: onPressed, style: _buttonStyle, child: Text(text));
final iconWidget = (icon != null) ? Icon(icon, size: size.fontSize * 1.25) : const SizedBox.shrink();
final labelWidget = (variant == ViamButtonVariant.iconOnly)
? const SizedBox.shrink()
: Text(
text,
style: TextStyle(fontWeight: (icon != null) ? FontWeight.bold : FontWeight.normal),
);
final first = (variant == ViamButtonVariant.iconTrailing) ? labelWidget : iconWidget;
final second = (variant == ViamButtonVariant.iconTrailing) ? iconWidget : labelWidget;
final widget = Row(mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [first, second]);
if (style == ViamButtonFillStyle.outline) {
child = OutlinedButton(onPressed: onPressed, style: _buttonStyle, child: widget);
} else {
child = TextButton(onPressed: onPressed, style: _buttonStyle, child: Text(text));
child = TextButton(onPressed: onPressed, style: _buttonStyle, child: widget);
}
if (variant == ViamButtonVariant.iconOnly) {
child = Tooltip(message: text, waitDuration: const Duration(seconds: 1), child: child);
}
return Theme(data: ThemeData(primarySwatch: role.materialColor), child: child);
}
Expand Down
4 changes: 4 additions & 0 deletions lib/widgets/camera_stream.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:logger/logger.dart';
import 'package:viam_sdk/viam_sdk.dart';

/// A widget to display a WebRTC stream from a [Camera].
class ViamCameraStreamView extends StatefulWidget {
/// The [Camera]
final Camera camera;

/// The [StreamClient] for the specific [Camera]
final StreamClient streamClient;

const ViamCameraStreamView({super.key, required this.camera, required this.streamClient});
Expand Down
4 changes: 3 additions & 1 deletion lib/widgets/joystick.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_joystick/flutter_joystick.dart';
import 'package:viam_sdk/viam_sdk.dart';

/// A [Joystick] to control a specific [Base]
class ViamBaseJoystick extends StatefulWidget {
const ViamBaseJoystick({
super.key,
required this.base,
});

/// The [Base]
final Base base;

@override
Expand All @@ -25,7 +27,7 @@ class _ViamBaseJoystickState extends State<ViamBaseJoystick> {
Text('Y: ${y.round()}% Z: ${z.round()}%'),
const SizedBox(height: 16),
Joystick(
listener: (callSetPower),
listener: callSetPower,
)
],
);
Expand Down
9 changes: 9 additions & 0 deletions lib/widgets/resources/base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@ import 'package:viam_sdk/viam_sdk.dart';
import '../camera_stream.dart';
import '../joystick.dart';

/// A widget to control a [Base].
///
/// This widget provides a joystick for moving a [Base],
/// along with displaying any camera streams that might be available on the robot.
class ViamBaseScreen extends StatefulWidget {
/// The [Base]
final Base base;

/// Any [Camera]s that should be streamed
final Iterable<Camera> cameras;

/// The current [RobotClient]
final RobotClient robotClient;

const ViamBaseScreen({
Expand Down
5 changes: 5 additions & 0 deletions lib/widgets/resources/board.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import 'package:viam_sdk/viam_sdk.dart';

import '../button.dart';

/// A widget to control a [Board].
///
/// Displays the status of any [DigitalInterrupts] or [AnalogReaders] in a data table.
/// Provides the ability to set specific GPIO pins to high/low states.
class ViamBoardWidget extends StatefulWidget {
/// The [Board]
final Board board;

const ViamBoardWidget({
Expand Down
27 changes: 20 additions & 7 deletions lib/widgets/resources/sensor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,22 @@ import 'package:viam_sdk/viam_sdk.dart';

import '../button.dart';

/// A widget to display data from a [Sensor].
///
/// Displays the data in a simple data table, with options for
/// displaying the time the data were retrieved,
/// automatic refreshing, and for controlling the refresh.
class ViamSensorWidget extends StatefulWidget {
/// The [Sensor]
final Sensor sensor;

/// How often to automatically refresh the data
final Duration? refreshInterval;

/// Whether to display the time the data were retrieved
final bool showLastRefreshed;

/// Whether to display controls for refreshing data
final bool showRefreshControls;

const ViamSensorWidget({
Expand Down Expand Up @@ -103,13 +115,14 @@ class _ViamSensorWidgetState extends State<ViamSensorWidget> {
Column(children: [
const SizedBox(height: 8),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
ViamButton(
onPressed: playPause,
text: isPaused ? 'Play' : 'Pause',
icon: isPaused ? Icons.play_arrow : Icons.pause,
variant: ViamButtonVariant.iconOnly,
style: ViamButtonFillStyle.ghost,
),
if (widget.refreshInterval != null)
ViamButton(
onPressed: playPause,
text: isPaused ? 'Play' : 'Pause',
icon: isPaused ? Icons.play_arrow : Icons.pause,
variant: ViamButtonVariant.iconOnly,
style: ViamButtonFillStyle.ghost,
),
const SizedBox(width: 8),
ViamButton(
onPressed: refresh,
Expand Down
21 changes: 21 additions & 0 deletions test/test_utils.dart
Original file line number Diff line number Diff line change
@@ -1 +1,22 @@
import 'package:flutter/material.dart';

int generateTestingPortFromName(String name) => 50000 + (name.hashCode % 10000);

class TestableWidget extends StatelessWidget {
const TestableWidget({
super.key,
required this.child,
});

final Widget child;

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: child,
),
);
}
}
90 changes: 90 additions & 0 deletions test/widget_tests/sensor_widget_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:viam_sdk/widgets.dart';

import '../test_utils.dart';
import '../unit_test/components/sensor_test.dart';

void main() {
group('ViamSensorWidget', () {
testWidgets('displays data', (tester) async {
final widget = TestableWidget(child: ViamSensorWidget(sensor: FakeSensor('sensor')));
await tester.pumpWidget(widget);

final dataTable = find.byType(DataTable);

expect(dataTable, findsOneWidget);
});

testWidgets('shows last refresh time', (tester) async {
final widget = TestableWidget(child: ViamSensorWidget(sensor: FakeSensor('sensor'), showLastRefreshed: true));
await tester.pumpWidget(widget);

final refreshButton = find.widgetWithIcon(ViamButton, Icons.refresh);
await tester.press(refreshButton);
await tester.pump();

final refreshTime = find.textContaining(RegExp(r'Updated at:.*'));

expect(refreshTime, findsOneWidget);
});

testWidgets('hides last refresh time', (tester) async {
final widget = TestableWidget(child: ViamSensorWidget(sensor: FakeSensor('sensor'), showLastRefreshed: false));
await tester.pumpWidget(widget);

final refreshButton = find.widgetWithIcon(ViamButton, Icons.refresh);
await tester.press(refreshButton);
await tester.pump();

final refreshTime = find.textContaining(RegExp(r'Updated at:.*'));

expect(refreshTime, findsNothing);
});

testWidgets('shows refresh controls', (tester) async {
final widget = TestableWidget(child: ViamSensorWidget(sensor: FakeSensor('sensor'), showRefreshControls: true));
await tester.pumpWidget(widget);

final playButton = find.widgetWithIcon(ViamButton, Icons.play_arrow);
final pauseButton = find.widgetWithIcon(ViamButton, Icons.pause);
final refreshButton = find.widgetWithIcon(ViamButton, Icons.refresh);

expect(playButton, findsNothing);
expect(pauseButton, findsOneWidget);
expect(refreshButton, findsOneWidget);

await tester.press(pauseButton);
await tester.pump();

expect(playButton, findsNothing);
expect(pauseButton, findsOneWidget);
});

testWidgets('hides refresh controls', (tester) async {
final widget = TestableWidget(child: ViamSensorWidget(sensor: FakeSensor('sensor'), showRefreshControls: false));
await tester.pumpWidget(widget);

final playButton = find.widgetWithIcon(ViamButton, Icons.play_arrow);
final pauseButton = find.widgetWithIcon(ViamButton, Icons.pause);
final refreshButton = find.widgetWithIcon(ViamButton, Icons.refresh);

expect(playButton, findsNothing);
expect(pauseButton, findsNothing);
expect(refreshButton, findsNothing);
});

testWidgets('hides play/pause when refresh interval is null', (tester) async {
final widget = TestableWidget(child: ViamSensorWidget(sensor: FakeSensor('sensor'), refreshInterval: null));
await tester.pumpWidget(widget);

final playButton = find.widgetWithIcon(ViamButton, Icons.play_arrow);
final pauseButton = find.widgetWithIcon(ViamButton, Icons.pause);
final refreshButton = find.widgetWithIcon(ViamButton, Icons.refresh);

expect(playButton, findsNothing);
expect(pauseButton, findsNothing);
expect(refreshButton, findsOneWidget);
});
});
}

0 comments on commit 20344fc

Please sign in to comment.