Flutter: How to test a method inside a Widgets State class? - unit-testing

I have a widget test going for a Drawer which I named DefaultDrawer. I would also like to test the methods I made inside DefaultDrawerState. For example,
class DefaultDrawerState extends State<DefaultDrawer> {
#override
Widget build(BuildContext context) {
// ..build..
}
Widget _circleAvatar() {
// a method I want to test
}
}
How can I test the method _circleAvatar() when the flutter_test finders can't seem to call the methods within my widget or its state?
Below is my test file right now, without the testing of methods inside the widget:
void main() {
testWidgets('Drawer Test', (WidgetTester tester) async {
final key = GlobalKey<ScaffoldState>();
await tester.pumpWidget(
MaterialApp(home: Scaffold(key: key, drawer: DefaultDrawer())));
// open drawer
key.currentState.openDrawer();
await tester.pump();
// did drawer open?
final drawerFinder = find.byType(Drawer);
expect(drawerFinder, findsOneWidget);
});
}

To be able to test a Widget, you'd need to render the UI on the integration test using WidgetTester.pumpWidget(Widget). Let `SampleWidget
class SampleWidget {
Widget getWidget() {
return Container(
key: Key('SampleWidget'),
color: Colors.green,
);
}
}
... and on the test, call the Widget directly with widgetTester.pumpWidget(SampleWidget().getWidget()); to render it for the test.
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets("Test Widget", (WidgetTester widgetTester) async {
bool found = false;
await widgetTester.pumpWidget(SampleWidget().getWidget());
widgetTester.allWidgets.forEach((Widget element) {
if(element.key.toString().contains('SampleWidget')){
debugPrint('Found Sample Widget!');
found = true;
}
});
expect(found, true);
});
}
The test should be able to find the widget element containing the key specified.
However, some Widgets on WidgetTester requires MaterialApp to be its parent. An example of this widget is Text - which requires Directionality, and Directionality can be found by using either MaterialApp or WidgetsApp widget. Rendering the Text Widget without MaterialApp on the test will fail. This error will be thrown.
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞════════════════════════
The following assertion was thrown building Text("Demo Widget"):
No Directionality widget found.
RichText widgets require a Directionality widget ancestor.
The specific widget that could not find a Directionality ancestor
was:
RichText
The ownership chain for the affected widget is: "RichText ← Text
← Center ← ColoredBox ← Container-[<'SampleWidget'>] ←
RepaintBoundary ← [root]"
Typically, the Directionality widget is introduced by the
MaterialApp or WidgetsApp widget at the top of your application
widget tree. It determines the ambient reading direction and is
used, for example, to determine how to lay out text, how to
interpret "start" and "end" values, and to resolve
EdgeInsetsDirectional, AlignmentDirectional, and other
*Directional objects.
As a workaround for this use case, you can wrap your Widget with MaterialApp.
class SampleWidget{
Widget getWidget() {
return MaterialApp(
home: Container(
key: Key('SampleWidget'),
color: Colors.green,
child: Center(
child: Text('Demo Widget'),
),
),
);
}
}

Related

Flutter - How to do a tick box filter for a list to narrow down search

I am very new to flutter and still learning. I am struggle on how to create a filter for a list of cars. E.g. tick a box to only show red cars.
I do a little bit of searching but I can't seem to figure out how to do it. I did see a "where" method but struggling to make sence of it.
What is the the best way of doing this and can you please point me in the right direction. Can't get my head about this one.
So to create a filter for your list. Lets assume that you have a class for your car:
Class Car {
final String carName;
final String color;
Car({this.carName, this.color});
}
And lets say you have some car objects:
List<Car> AllCars = [
new Car(carName: "McQueen",color: "red"),
new Car(carName: "Mater",color: "rusty"),
];
Now you create a statefulWidget for your list:
class ListPage extends StatefulWidget{
#override
_listPageState createState() => new _listPageState();
}
class _listPageState extends State<ListPage> {
List<Car> _RedCars = null;
#override
void initState() {
super.initState();
_RedCars = AllCars.where((i) => i.color == "red").toList();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Container(
child: new Text(
"Boom!!! You are done. Now build your list on the page."
),
),
);
}
}
So, what you are trying to do can be achieved by this. Now all you have to do is to do it dynamic, show this list on your page. Remember the more your struggle the more you learn.

Test widget using the BLoC pattern

I have been learning Flutter/Dart and the BLoC Pattern. I used this article as my starting point:
https://www.didierboelens.com/2018/08/reactive-programming---streams---bloc/
I have the bloc class and widget working, but I can't figure out how to test the widget. I'm using a BlocProvider as described in the article, but I can't figure out how to provide the widget with a mocked bloc class.
If I have code like this:
#override
Widget build(BuildContext context) {
final ProfileBloc profileBloc = BlocProvider.of<ProfileBloc>(context);
return Scaffold(
body: Container(
child: StreamBuilder<AuthModel>(
stream: profileBloc.outAuthModel,
initialData: null,
builder: (BuildContext context, AsyncSnapshot<AuthModel> snapshot) {
if (snapshot.hasData) {
return buildProfilePage(context, snapshot.data.profile);
}
return buildStartPage();
},
),
));
}
I want to mock my ProfileBloc, but it is created in my build() function and requires context. How can I test this widget? I think I need a way to pass in a mocked ProfileBloc, but I can not figure out how to do it. I want to ensure that the widget behaves as intended.
I had the exact same problem when testing a widget and was able to solve it. Here's the "Before Code" that didn't work and "After Code" that did the trick...
BEFORE CODE
Notice that when pumping the widget MaterialApp is set as the top most widget.
Future<Null> _buildRideCard(WidgetTester tester) async {
await tester.pumpWidget(MaterialApp( // top most widget
localizationsDelegates: [
AppLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
//some other stuff, irrelevant for this example
));
}
AFTER CODE
Notice how MaterialApp widget is now wrapped with BlocProvider and it's blocProviders property is given a list of Blocs that the widget test needs. This fixed my problem and now I don't have any context issues with the bloc in my widget test. Hope it helps ;)
Future<Null> _buildRideCard(WidgetTester tester) async {
await tester.pumpWidget(BlocProviderTree( // <- magic #1
blocProviders: [ <- magic #2
BlocProvider<RideDetailsBloc>(
bloc: RideDetailsBloc(_setupRidesRepo()))
],
child: MaterialApp(
localizationsDelegates: [
AppLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
//some other stuff, irrelevant for this example
),
));
}
In this example your using BlocProvider to get your ProfileBloc, but you could directly make a new bloc using final ProfileBloc profileBloc = ProfileBloc;. Using an outside bloc shouldn't be important, because this is after a widget test.

Why does this CSS transition event not fire when two classes are added?

CSS
.horizontalTranslate {
-webkit-transition: 3s;
}
.secondClass {
background: red;
}
HTML
<div class='box'></div>
JS
var box = document.querySelector('.box');
box.addEventListener('webkitTransitionEnd', function(evt) {
alert('Finished transition!');
}, false);
function transitionBox() {
// this works
box.className = 'horizontalTranslate';
// this does not work
// box.className = 'secondClass horizontalTranslate';
}
setTimeout(transitionBox, 100);
Why does the transition event not fire when two classes are added rather than one? I've also tried chaining my CSS selector, a la .secondClass.horizontalTranslate { ... }.
The reason is that box.className = 'horizontalTranslate'; is actually removing styling, causing the CSS transition to actually happen. But when I set box.className = 'secondClass horizontalTranslate';, the styling of the DOM node is not changing and no event is fired.
Another way to write transitionBox is this:
function transitionBox() {
box.classList.add('horizontalTranslate');
box.classList.add('blue');
}
If blue changes the styling of box, this works too.

QtQuick, Dynamic Images and C++

I'm new to Qt, and from what I've read on qt-project.org and other places; QtQuick seems like an attractive option because of its ability to work on both pointer and touch based devices. My problem is getting it to work well with c++.
I decided to write a variant of Conway's Game of Life as a next step after "Hello World". I am thoroughly mystified as to how to get the "board" -- a [height][width][bytes-per-pixel] array of char -- integrated into the scene graph.
Basically, the process is that the "LifeBoard" iterates through its rules and updates the char*/image. I've got this simple QML:
:::QML
ApplicationWindow {
id: life_app_window
visible: true
title: qsTr("Life")
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Quit")
onTriggered: Qt.quit();
}
}
}
toolBar: ToolBar {
id: lifeToolBar;
ToolButton {
id: toolButtonQuit
text: qsTr("Quit")
onClicked: Qt.quit()
}
ToolButton {
id: toolButtonStop
text: qsTr("Stop")
enabled: false
//onClicked:
}
ToolButton {
id: toolButtonStart
text: qsTr("Start")
enabled: true
//onClicked: //Start life.
}
ToolButton {
id: toolButtonReset
text: qsTr("Stop")
// onClicked: //Reset life.
}
}
Flow {
id: flow1
anchors.fill: parent
//*****
// WHAT GOES HERE
//*****
}
statusBar: StatusBar {
enabled: false
Text {
// Get me from number of iterations
text: qsTr("Iterations.")
}
}
}
I want to image to come from a class with a api kinda like this:
class Life {
public:
QImage getImage() {}
// Or
char* getPixels(int h, int w, QImage::Format_ARGB8888) {}
}
I have no clue, and hours wading through tutorials did not help. How does one link a char* image in c++ to a ??? in QML so that the QML can start/stop the "Life" loop and so that the "Life" loop and update the char array and notify QML to redraw it?
Note: I've looked at subclassing QQuickImageProvider based on the info here. The problem with this approach is that I cannot see how to let c++ "drive" the on screen image. I wish to pass control from QML to c++ and let c++ tell QML when to update the display with the changed image. Is there a solution with this approach? Or another approach entirely.
First way to do that would be creating a Rectangle for each game pixel in QML, which might be fancy for a 8x8 board, but not for a 100x100 board, since you need to write the QML code manually for each pixel.
Thus I'd go for images created in C++ and exposed to QML. You call them via an image provider to allow asynchronous loading. Let Life do the logic only.
The image is called from QML like this:
Image {
id: board
source: "image://gameoflife/board"
height: 400
width: 400
}
Now gameoflife is the name of the image provider and board the so-called id you can use later.
Register gameoflife in you main.cpp
LifeImageProvider *lifeIP = new LifeImageProvider(life);
engine.addImageProvider("gameoflife", lifeIP);
where engine is your main QQmlApplicationEngine and life an instance of your Life game engine.
LifeImageProvider is your class to create pixeldata. Starts somehow like
class LifeImageProvider : public QQuickImageProvider
{
public:
LifeImageProvider(Life *myLifeEngine);
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize);
private:
Life *myLifeEngine_;
};
The important method is requestPixmap, which is called from QML. You need to implement it.
To refresh the game board when Life sends a stateChanged() signal, expose life as a global object to QML:
context->setContextProperty("life", &life);
You can bind the signal to QML
Image {
id: board
source: "image://gameoflife/board"
height: 400
width: 400
}
Connections {
target: life
onStateChanged: {
board.source = "image://gameoflife/board?" + Math.random()
// change URL to refresh image. Add random URL part to avoid caching
}
}
Just for fun, and at the risk of downvotes for a completely tangential answer, here's a GameOfLife implemented entirely in QML, just put it in a .qml file and run it with qmlscene. Works on Qt 5.3.0, and runs surprisingly (to me) fast on an old Core 2 Duo lappy. I'm sure it'll never be as fast/efficient as a C++ QQuickImageProvider based solution though, but it does make the point it's possible to do quite a lot in QML without resorting to C++.
import QtQuick 2.2
Rectangle {
id: main
width: 640
height: 640
color: '#000088'
Timer {
interval: 1000/60
running: true
repeat: true
onTriggered: {advance();display();}
}
Component {
id: cellComponent
Rectangle {
objectName: 'cell'
property int row: 0
property int col: 0
x: main.width/2+width*col
y: main.height/2+height*row
width: 5
height: 5
radius: 2
smooth: true
color: '#ffcc00'
}
}
property var cells: null
Component.onCompleted: {
cells=[[-1, 0],[-1, 1],[ 0,-1],[ 0, 0],[ 1, 0]];
display();
}
function display() {
// Just completely regenerate display field each frame
// TODO: might be nicer to do differential updates, would allow birth/death animations
// Nuke all previously displayed cells
for (var i=0;i<children.length;i++) {
if (children[i].objectName=='cell') {
children[i].destroy();
}
}
// Show current set of cells
for (var i=0;i<cells.length;i++) {
var c=cellComponent.createObject(
main,
{'row':cells[i][0],'col':cells[i][1]}
);
}
}
function advance() {
// Build a hash of the currently alive cells and a neighbour count (includes self)
var a=new Object;
var n=new Object;
for (var i=0;i<cells.length;i++) {
var p=cells[i]
var r=p[0];
var c=p[1];
if (!(r in a)) a[r]=new Object;
a[r][c]=1;
for (var dr=r-1;dr<=r+1;dr++) {
for (var dc=c-1;dc<=c+1;dc++) {
if (!(dr in n)) n[dr]=new Object;
if (!(dc in n[dr])) n[dr][dc]=0;
n[dr][dc]+=1;
}
}
}
// For all live cells, assess viability
var kill=[];
var stay=[];
for (var r in a) {
for (var c in a[r]) {
if (n[r][c]-1<2 || n[r][c]-1>3)
kill.push([Number(r),Number(c)]);
else
stay.push([Number(r),Number(c)]);
}
}
// For neighbours of live cells, assess potential for births
var born=[];
for (var r in n) {
for (var c in n[r]) {
if (!((r in a) && (c in a[r]))) {
if (n[r][c]==3)
born.push([Number(r),Number(c)]);
}
}
}
cells=stay.concat(born)
}
}
And for a pure QML version using GLSL (via a recursive QML ShaderEffect) to compute the Game of Life rules on GPU see here.

Adding a custom Authors category to Gtk::AboutDialog class

I was wondering if there was a way to set a custom Authors category in a Gtk::AboutDialog class via gtkmm. I know there are the following methods:
set_artists()
set_authors()
set_documenters()
set_translator_credits()
But I wanted to add a custom category. Right now I have a program that accepts a bunch of plugins, so on startup when it scans for plugins I would like to populate a "Plugins" page on the about screen once you click credits that shows all of the plugin authors' names (removing duplicates of course). The logic is already there, but it looks quite odd adding them to the artists or documenters categories where they certainly do not belong.
Is there an easy way to add a new category besides rolling my own?
Nice question! In GTK 3, this is fairly easy. You have to do some manipulation of the About dialog's internal children, which may change in future releases, so be warned!
I've written a quick-n-dirty example in Vala that does what you want. That was faster for me because I almost never use Gtkmm. It shouldn't be too hard to translate though.
using Gtk;
int main(string[] args)
{
Gtk.init(ref args);
var dialog = new AboutDialog();
// Fetch internal children, using trickery
var box = dialog.get_child() as Box;
Box? box2 = null;
ButtonBox? buttons = null;
Notebook? notebook = null;
box.forall( (child) => {
if(child.name == "GtkBox")
box2 = child as Box;
else if(child.name == "GtkButtonBox")
buttons = child as ButtonBox;
});
box2.forall( (child) => {
if(child.name == "GtkNotebook")
notebook = child as Notebook;
});
// Add a new page to the notebook (put whatever widgets you want in it)
var plugin_page_index = notebook.append_page(new Label("Plugin 1\nPlugin 2"),
new Label("Plugins"));
// Add a button that toggles whether the page is visible
var button = new ToggleButton.with_label("Plugins");
button.clicked.connect( (button) => {
notebook.page = (button as ToggleButton).active? plugin_page_index : 0;
});
buttons.pack_start(button);
buttons.set_child_secondary(button, true);
// Set some other parameters
dialog.program_name = "Test Program";
dialog.logo_icon_name = Gtk.Stock.ABOUT;
dialog.version = "0.1";
dialog.authors = { "Author 1", "Author 2" };
dialog.show_all(); // otherwise the new widgets are invisible
dialog.run();
return 0;
}
In GTK 2, this is much more difficult, although probably not impossible. You have to connect to the Credits button's clicked signal, with a handler that runs after the normal handler, and then get a list of toplevel windows and look for the new window that opens. Then you can add another page to that window's GtkNotebook.
I would suggest doing it a little differently: add a Plugins button to the action area which opens its own window. Then you don't have to go messing around with internal children. Here's another Vala sample:
using Gtk;
class PluginsAboutDialog : AboutDialog {
private Dialog _plugins_window;
private Widget _plugins_widget;
public Widget plugins_widget { get {
return _plugins_widget;
}
set {
var content_area = _plugins_window.get_content_area() as VBox;
if(_plugins_widget != null)
content_area.remove(_plugins_widget);
_plugins_widget = value;
content_area.pack_start(value);
}}
public PluginsAboutDialog() {
_plugins_window = new Dialog();
_plugins_window.title = "Plugins";
_plugins_window.add_buttons(Stock.CLOSE, ResponseType.CLOSE, null);
_plugins_window.response.connect((widget, response) => { widget.hide(); });
var buttons = get_action_area() as HButtonBox;
// Add a button that opens a plugins window
var button = new Button.with_label("Plugins");
button.clicked.connect( (button) => {
_plugins_window.show_all();
_plugins_window.run();
});
button.show();
buttons.pack_start(button);
buttons.set_child_secondary(button, true);
}
public static int main(string[] args) {
Gtk.init(ref args);
var dialog = new PluginsAboutDialog();
// Make a widget for the plugins window
var can_be_any_widget = new Label("Plugin 1\nPlugin 2");
dialog.plugins_widget = can_be_any_widget;
// Set some other parameters
dialog.program_name = "Test Program";
dialog.logo_icon_name = Gtk.Stock.ABOUT;
dialog.version = "0.1";
dialog.authors = { "Author 1", "Author 2" };
dialog.run();
return 0;
}
}