Flutter: testing widgets - testing

what I'm trying to test is:
the user taps a button and the snackbar is shown for five second.
Here is the scaffold with its key:
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(debugLabel: "scaffoldState");
...
builder: (context, viewModel) => Scaffold(
key: _scaffoldKey,
Then the code for the button and the snackBar:
child: FlatButton(key: Key('loginButton'), onPressed: () {
validate();
...
validate() {
// FormState object can be used to save, reset, and validate
// every FormField that is a descendant of the associated Form.
final FormState form = _formKey.currentState;
if (form.validate()) {
form.save();
form.reset();
//implementation of the snackBar:
_scaffoldKey.currentState.showSnackBar(new SnackBar(
duration: new Duration(seconds: 5)
...
And I am trying to test this behaviour in this way (I'm using also Flutter redux):
void main(){
final store = Store<AppState>(reducer, initialState: AppState.initialState());
testWidgets("MaterialApp local", (WidgetTester tester) async{
await tester.pumpWidget(new StoreProvider(
store: store,
child: MaterialApp(
home: LoginView(),
)));
await tester.pumpAndSettle();
await tester.press(find.byKey(Key("loginButton")));
expect();
I don't know what to put inside expect. I was trying to take the GlobalKey using find.ByKey(Key)) and to search for the debugLabel, but it doesn't seem to work. I'm trying since 2 days, but I can't find a way out.

It might be a little late to answer this, but since it was the second result on Google when typing "Flutter test Snackbar", I will just give my two cents:
It's actually pretty simple. Don't think of the Snackbar as being something completely different only because it only resides at the bottom for a few seconds. Lastly, the Snackbar is just another Widget.
Keeping this in mind you can test it as follows:
testWidgets("MaterialApp local", (WidgetTester tester) async{
...
// Test if the Snackbar appears
expect(find.byType(SnackBar), findsOneWidget);
// Test if it contains your text
expect(find.text('TAPS!'), findsOneWidget);
});

i actually met the same condition as you did, and all i did is put a text as the content of the snackbar.
for showing the snackbar, the code i use is
scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("TAPS!"),));
}, "SEE ALL", backgroundColor: Colors.red, textColor: Colors.blue)
and i expect to find a text with the same content as i put in, and add findsOneWidget.
expect(find.text("TAPS!"), findsOneWidget);

Related

Add test description in report

Is there a way to add custom text in testcafe reports?
I would like to add a short description in the reports, so when somebody else checks it to be able to understand what the test does (especially when the test is passed, only the test name appears in the report and I cannot write too much text in the name of the test,it makes no sense). The problem is that I have different functions checking some menus and only 1 test scenario. I would like to add a text to know which functions were called.
console.log('custom text') will write the text only in the console
this.write('custom text') used inside a async function gives an error.
class goThroughAllMenus{
constructor(){
}
async f_CheckHomeMenus() {
//Description: This function is going through all submenus under Home page
// and checks that the pages are 'up and running'.
this.write(`Running test for Home menu`)
await t
//Hjem
.click(StartPage.HomeMenus.menuHome)
.expect(StartPage.StartPage.StartSubMenu.exists).ok()
}
}
There is no built-in capability to pass custom data to the reporter. You can ping the already existing issue in the TestCafe repo. However, if you just want to send common information about the whole test block, you can use meta and create a custom reporter to show this information.
//reporter.js
export default function () {
return {
async reportTaskStart (startTime, userAgents, testCount) {
},
async reportFixtureStart (name, path, meta) {
},
async reportTestStart (name, meta) {
},
async reportTestDone (name, testRunInfo, meta) {
this.write(meta );
},
async reportTaskDone (endTime, passed, warnings, result) {
}
};
}
Also, you can create a fork of any existing reporter and enhance one.

Platform specific code in Flutter integration tests

I have an integration test that has to open a time picker, but each platform has a different implementation of time picker. Therefor the flow of the integration test must differ between Android and iOS.. how can I achieve this?
I tried using the Platform class like this inside the test file, but it doesnt work:
//* 5) Choose time
await driver.tap(find.byValueKey('addRideTimePicker'));
if (Platform.isAndroid) {
await driver.tap(find.text("V REDU"));
}
if (Platform.isIOS) {
await driver.tap(find.text("OK"));
}
Any help would be much appreciated, thanks in advance
Not sure how you implemented TimePicker, but usually CupertinoTimePicker is the widget that renders the time picker on screen. Also, depending on where do you want to show it on screen, using CupertinoTimePicker, it renders equally from UI perspective on both platforms. For example, you may show the time picker inside Container or inside bottomsheet. A sample code to show time picker inside bottomsheet is as below:
body: Center(
child: RaisedButton(
child: Text('Click'),
onPressed: () {
showModalBottomSheet(context: context, builder: (BuildContext builder) {
return Container(
child: time()
);
});
},
)
),
Widget time() {
return CupertinoTimerPicker(
mode: CupertinoTimerPickerMode.hms,
minuteInterval: 1,
secondInterval: 1,
initialTimerDuration: initialtimer,
onTimerDurationChanged: (Duration changedtimer) {
setState(() {
initialtimer = changedtimer;
});
},
);
}
Above code shows time picker equally on both platforms as below:
This way, you may not need to use Platform class as you mentioned and directly in your flutter driver test, you may first identify the elements displayed in the bottomsheet and accordingly tap or perform actions you need.
Hope this answers your question.

Flutter - how to get Text widget on widget test

I'm trying to create a simple widget test in Flutter. I have a custom widget that receives some values, composes a string and shows a Text with that string. I got to create the widget and it works, but I'm having trouble reading the value of the Text component to assert that the generated text is correct.
I created a simple test that illustrates the issue. I want to get the text value, which is "text". I tried several ways, if I get the finder asString() I could interpret the string to get the value, but I don't consider that a good solution. I wanted to read the component as a Text so that I have access to all the properties.
So, how would I read the Text widget so that I can access the data property?
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('my first widget test', (WidgetTester tester) async {
await tester
.pumpWidget(MaterialApp(home: Text("text", key: Key('unit_text'))));
// This works and prints: (Text-[<'unit_text'>]("text"))
var finder = find.byKey(Key("unit_text"));
print(finder.evaluate().first);
// This also works and prints: (Text-[<'unit_text'>]("text"))
var finderByType = find.byType(Text);
print(finderByType.evaluate().first);
// This returns empty
print(finder.first.evaluate().whereType<Text>());
// This throws: type 'StatelessElement' is not a subtype of type 'Text' in type cast
print(finder.first.evaluate().cast<Text>().first);
});
}
I got it working. I had to access the widget property of the Element, and then cast it as text:
var text = finder.evaluate().single.widget as Text;
print(text.data);
Please check this simple example.
testWidgets('Test name', (WidgetTester tester) async {
// findig the widget
var textFind = find.text("text_of_field");
// checking widget present or not
expect(textFind, findsOneWidget);
//getting Text object
Text text = tester.firstWidget(textFind);
// validating properies
expect(text.style.color, Colors.black);
...
...
}
You can use find.text
https://flutter.io/docs/cookbook/testing/widget/finders#1-find-a-text-widget
testWidgets('finds a Text Widget', (WidgetTester tester) async {
// Build an App with a Text Widget that displays the letter 'H'
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Text('H'),
),
));
// Find a Widget that displays the letter 'H'
expect(find.text('H'), findsOneWidget);
});

WinJS Listview shows undefined when navigating quickly

I have a WinJS application with listviews in which if quickly navigate between pages before the listview is fully loaded, the next page shows the listview with all elements in it bound as "undefined".
So say I have a hub page with a "to do" that is filtered to only show 6 items, and there is a header that navigates to the full "to do" page, when the hub page is displayed but before it is fully loaded I click on the header link to the "to do" page, the app then goes to the "to do" page, but the items show up with all the properties in the tile as "undefined".
I am using IndexedDB as my data store.
My home page code looks like this:
WinJS.UI.Pages.define("/pages/home/home.html", {
ready: function (element, options) {
WinJS.Utilities.query("a").listen("click", function (e) {
e.preventDefault();
WinJS.Navigation.navigate(e.currentTarget.href);
}, false);
viewModel = new HomeViewModel(element);
viewModel.load(); //loads from indexed db
},
//etc...
To Do Page:
WinJS.UI.Pages.define("/pages/ToDo/ToDo.html", {
ready: function (element, options) {
viewModel = new ToDoViewModel(element);
viewModel.load();
},
etc//
I know there isn't much to go off, but any ideas would be appreciated.
Also tips on how to debug something like this would be great.
Update
I narrowed it down to this one line from the Hub Page:
myLib.GetData(todaysDate, function (result) {
that.trendsModel.today = result;
WinJS.Binding.processAll(that.el.querySelector("#dataPanel"), that.trendsModel); //<--Right Here
});
If I remove that, then when I load the second page the data doesn't show as undefined. What is interesting is the data initially shows correctly on the second page and then it changes to "undefined".
Solution
My fix:
myLib.GetData(todaysDate, function (result) {
var element = that.el.querySelector("#dataPanel");
that.trendsModel.today = result;
if(element) {
WinJS.Binding.processAll(element, that.trendsModel);
}
});
At the point when when the callback returns, I am already on the second page. So the selector was not found returning null. If you pass null to processAll it tries to bind the whole page which is why I was able to see the correct data for a second then it changes to undefined...Wow, what a doozy. I guess it makes sense but what a pain to find.
Hope it helps someone in the future :)
Your ToDoViewModel, and HomeViewModel need to be observable. This means they need to mix in from WinJS.Binding.mixin, and for the properties that you pull in asynchronously, they need to call this.notify("propertyName", newVal, oldVal) from the property setter.
Note that you need to have getter/setter properties. e.g.
var bindingBase = WinJS.Class.mix(function() {}, WinJS.Binding.mixin);
WinJS.Namespace.define("YourNamespace", {
ToDoViewModel: WinJS.Class.derive(bindingBase, function constructor() {
}, {
_titleStorage: "",
title: {
get: function() { return this._titleStorage; },
set: function(newValue) {
if(newValue === this._titleStorage) {
return;
}
var old = this._titleStorage;
this._titleStorage = newValue;
this.notify("title", newValue, old);
}
}
}),
});
myLib.GetData(todaysDate, function (result) {
var element = that.el.querySelector("#dataPanel");
that.trendsModel.today = result;
if(element) {
WinJS.Binding.processAll(element, that.trendsModel);
}
});
At the point when when the callback returns, I am already on the second page. So the selector was not found returning null. If you pass null to processAll it tries to bind the whole page which is why I was able to see the correct data for a second then it change to undefined...Wow, what doozy. I guess it makes sense but what a pain to find.

jqGrid: different navigator buttons depending on login status

I want to use different navigator buttons in jqGrid depending on login status.
for example: if the user is logged in then add/delete/edit button appeared.
Any ideas?
Thanks in advance.
It is possible to add buttons programmatically using the navButtonAdd method (for the navigation bar) and the toolbarButtonAdd method for the toolbar. For example:
jQuery("#grid").toolbarButtonAdd('#t_meters',{caption:"MyButton",
id: "t_my_button",
title: "Do my button action",
buttonicon: 'ui-icon-edit',
onClickButton:function(){
// Button handle code goes here...
}
});
And:
jQuery("#grid")..navButtonAdd('#pager',{
id: "t_my_button",
title: "Do my button action",
buttonicon: 'ui-icon-edit',
onClickButton:function(){
// Button handle code goes here...
}
});
For more information see the Custom Buttons on the Wiki.
Anyway, once this code is in place, you can detect login status server-side. Then use this knowledge to generate client code that only adds the buttons to your grid if the user is supposed to have access to them.
You can also use for example userdata (see http://www.trirand.com/jqgridwiki/doku.php?id=wiki:retrieving_data#user_data) to send information about buttons which you need to have in the navigator. userdata should be set by server. Then with respect of:
var navGridParams = jQuery("grid_id").getGridParam('userData');
// var navGridParams = { edit: false, add: false, search: true }
you can get the data set by the server.
Now the typical call like:
jQuery("grid_id").navGrid('#pager', { edit: false, add: false, search: true });
You should place not after creating of jqGrid, but inside of than inside of loadComplete. So the code could looks like following:
var isNavCreated = false;
jQuery('#list').jqGrid({
// ...
loadComplete: function () {
var grid = jQuery("grid_id");
if (isNavCreated === false) {
isNavCreated = true;
var navGridParams = grid.getGridParam('userData');
grid.navGrid('#pager', navGridParams);
}
},
// ...
});
Another option that I see, is to use cookie instead of userdata to send information about navGridParams back to the client.