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.
Related
With AppKit I can do the following to bind multiple items to the same TextField, so when the TextField changes, they all update:
let options: [NSBindingOption : Any] = [NSBindingOption.validatesImmediately: true,
NSBindingOption.allowsEditingMultipleValuesSelection: true,
NSBindingOption.multipleValuesPlaceholder: "Multiple",
NSBindingOption.nullPlaceholder: "None",
NSBindingOption.conditionallySetsEditable: true]
textField.bind(.value, to: arrayController, withKeyPath: "selection.houseName", options: options)
In SwiftUI, I can bind a single item to a TextField like this:
TextField("My Text", text: $text)
However, that's just one item, I'm after the same behaviour as AppKit.
I could imagine one could write a custom binding, something like:
let propertyList = ["property1", "property2"]
let multipleValues = Binding<String>(
get: {
// return value if all items in propertyList are the same,
// else we need to return "Multiple", but not make "Multiple" editable text
},
set: { newValue in
for property in propertyList {
myObservable.setValue(newValue, forKeyPath: property)
}
}
)
TextField("My Text", text: multipleValues)
However, it seems I have to write the code to handle, null, multiple etc, which AppKit gives for free.
Is there a native SwiftUI system that has this behaviour?
Should I stick with AppKit for this? (Although, I'd like to move to SwiftUI)
Thank you for any advice here
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.
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'),
),
),
);
}
}
I have been following the tutorial at ionic2 SearchBar to work on the filter functionality.
The question is, I am not able to figure out how to get onCancel and onClear to work.
Steps:
1) Enter some keywords in SearchBar. It calls the onInput event and I get the value from searchItem.target.value unlike in tutorial which just uses searchItem.value
2) Now i try to click on clear "x" or Cancel button and it calls the onClear/onCancel event which is immediately followed by onInput event. And during this call, it does not find the searchItem.target.value and results in undefined due to which it breaks the functionality.
Can anyone provide more in depth details on how this works?
i fixed it in tutorial sample for ionic2 by stopping onClear event propagation
import {Component} from '#angular/core';
#Component({
selector: 'my-search',
template: '<ion-toolbar primary><ion-searchbar (input)="onInput($event)" (ionClear)="onClear($event)"></ion-searchbar></ion-toolbar><ion-content><ion-list><ion-item *ngFor="let item of items">{{ item }}</ion-item></ion-list></ion-content>'
})
export class HomePage {
items = [];
constructor() {
this.initializeItems();
}
initializeItems() {
this.items = [
'Angular 1.x',
'Angular 2',
'ReactJS',
'EmberJS',
'Meteor',
'Typescript',
'Dart',
'CoffeeScript'
];
}
onClear(ev)
{
this.initializeItems();
ev.stopPropagation();
}
onInput(ev) {
// Reset items back to all of the items
this.initializeItems();
// set val to the value of the searchbar
var val = ev.target.value;
// if the value is an empty string don't filter the items
if (val && val.trim() != '') {
this.items = this.items.filter((item) => {
return (item.toLowerCase().indexOf(val.toLowerCase()) > -1);
})
}
}
}
Is there a way to get the paper for an element by referencing the element?
I'm creating elements in a loop and with each element i'm creating a new Raphael(...). See sample below.
Basically I want to stop the animation on click, but paper is undefined and calling stop() on the element itself doesn't work either.
$.each(el,function(key,value)
{
var li = $("<li>",{id:"item"+key).appendTo("#myUl");
var ppr = new Raphael($("item"+key),get(0),48,48);
//... do stuff like animate ...
li.click(function()
{
console.log($(this).paper); //undefined
})
})
I was wondering about a closure like below to capture the paper, so when the anonymous func runs, it has the variable captured.
Note, I'm not sure this is the best method overall, something feels a bit clunky about creating a new paper each time, but just trying to address the specific issue.
Untested code, but if you can get it on a fiddle, I think it should be possible to sort.
$.each(el,function(key,value)
{
var li = $("<li>",{id:"item"+key).appendTo("#myUl");
var ppr = new Raphael($("item"+key),get(0),48,48);
(function() {
var myPaper = ppr;
li.click(function()
{
console.log(myPaper);
})
})();
})
You can also attach the paper to the element's "data" using https://api.jquery.com/data/
$.each(el,function(key,value)
{
var li = $("<li>",{id:"item"+key).appendTo("#myUl");
var ppr = new Raphael($("item"+key),get(0),48,48);
li.data("paper", ppr ); // SAVE
li.click(function()
{
console.log($(this).data("paper")); // LOAD
})
})