Created
November 18, 2021 13:23
-
-
Save alfianyusufabdullah/f03d3d922f80e871322e1d26e304a9c8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter_fundamental/common/json_helper.dart'; | |
import 'package:flutter_fundamental/data/network_data_state.dart'; | |
import 'package:flutter_fundamental/data/network_service.dart'; | |
import 'package:flutter_test/flutter_test.dart'; | |
import 'package:mock_web_server/mock_web_server.dart'; | |
import 'constant.dart'; | |
void main() { | |
MockWebServer _server = MockWebServer(); | |
NetworkService _service; | |
setUp(() async { | |
await _server.start(); | |
_service = NetworkService(_server.url); | |
}); | |
tearDown(() { | |
_server.shutdown(); | |
}); | |
group("Getting All Restaurant Testing", () { | |
test("get all restaurant should be success", () async { | |
var expectedState = NetworkDataState( | |
data: JsonHelper().parseRestaurantList(restaurantJson)); | |
_server.enqueue(body: restaurantJson, httpCode: 200); | |
var actualState = await _service.getRestaurants(); | |
expect(actualState, isA<NetworkDataState>()); | |
expect(actualState.isError, false); | |
expect(actualState.data[0].name, expectedState.data[0].name); | |
expect(actualState.data.length, expectedState.data.length); | |
}); | |
test("get all restaurant should be success but restaurant is empty", | |
() async { | |
var expectedState = NetworkDataState( | |
data: JsonHelper().parseRestaurantList(emptyRestaurantJson)); | |
_server.enqueue(body: emptyRestaurantJson, httpCode: 200); | |
var actualState = await _service.getRestaurants(); | |
expect(actualState, isA<NetworkDataState>()); | |
expect(actualState.isError, false); | |
expect(actualState.data.isEmpty, true); | |
expect(actualState.data.length, expectedState.data.length); | |
}); | |
test("get all restaurant should be fail", () async { | |
_server.enqueue(httpCode: 404); | |
var actualState = await _service.getRestaurants(); | |
expect(actualState, isA<NetworkDataState>()); | |
expect(actualState.isError, true); | |
expect(actualState.data == null, true); | |
}); | |
}); | |
group("Search Restaurant By Query Testing", () { | |
test("search restaurant should be success", () async { | |
var expectedState = NetworkDataState( | |
data: JsonHelper().parseRestaurantList(restaurantJson)); | |
_server.enqueue(body: restaurantJson, httpCode: 200); | |
var actualState = await _service.getRestaurantsByQuery("any"); | |
expect(actualState, isA<NetworkDataState>()); | |
expect(actualState.isError, false); | |
expect(actualState.data[0].name, expectedState.data[0].name); | |
expect(actualState.data.length, expectedState.data.length); | |
}); | |
test("search restaurant should be success but restaurant is empty", | |
() async { | |
var expectedState = NetworkDataState( | |
data: JsonHelper().parseRestaurantList(emptyRestaurantJson)); | |
_server.enqueue(body: emptyRestaurantJson, httpCode: 200); | |
var actualState = await _service.getRestaurantsByQuery("any"); | |
expect(actualState, isA<NetworkDataState>()); | |
expect(actualState.isError, false); | |
expect(actualState.data.isEmpty, true); | |
expect(actualState.data.length, expectedState.data.length); | |
}); | |
test("search restaurant should be fail", () async { | |
_server.enqueue(httpCode: 404); | |
var actualState = await _service.getRestaurantsByQuery("any"); | |
expect(actualState, isA<NetworkDataState>()); | |
expect(actualState.isError, true); | |
expect(actualState.data == null, true); | |
}); | |
}); | |
group("Detail Restaurant Testing", () { | |
test("get restaurant detail should be success", () async { | |
var expectedState = NetworkDataState( | |
data: JsonHelper().parseRestaurantDetail(detailRestaurantJson)); | |
_server.enqueue(body: detailRestaurantJson, httpCode: 200); | |
var actualState = await _service.getRestaurantById("any"); | |
expect(actualState, isA<NetworkDataState>()); | |
expect(actualState.isError, false); | |
expect(actualState.data.id, expectedState.data.id); | |
expect(actualState.data.name, expectedState.data.name); | |
expect( | |
actualState.data.description, expectedState.data.description); | |
expect(actualState.data.pictureId, expectedState.data.pictureId); | |
expect(actualState.data.city, expectedState.data.city); | |
expect(actualState.data.address, expectedState.data.address); | |
expect(actualState.data.rating, expectedState.data.rating); | |
}); | |
test("get restaurant detail should be fail", () async { | |
_server.enqueue(httpCode: 404); | |
var actualState = await _service.getRestaurantById("any"); | |
expect(actualState, isA<NetworkDataState>()); | |
expect(actualState.isError, true); | |
expect(actualState.data == null, true); | |
}); | |
}); | |
group("Publishing Restaurant Review Testing", () { | |
test("publishing review should be success", () async { | |
var expectedState = | |
NetworkDataState(data: JsonHelper().parseReviewStatus(reviewsJson)); | |
_server.enqueue(body: reviewsJson, httpCode: 200); | |
var actualState = await _service.publishReview("any review", "any"); | |
expect(actualState, isA<NetworkDataState>()); | |
expect(actualState.isError, false); | |
expect(actualState.data, expectedState.data); | |
}); | |
test("publishing review should be fail", () async { | |
_server.enqueue(httpCode: 404); | |
var actualState = await _service.publishReview("any review", "any"); | |
expect(actualState, isA<NetworkDataState>()); | |
expect(actualState.isError, true); | |
expect(actualState.data == null, true); | |
}); | |
}); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter_fundamental/common/json_helper.dart'; | |
import 'package:flutter_fundamental/data/network_service.dart'; | |
import 'package:flutter_fundamental/provider/restaurant_detail_page_provider.dart'; | |
import 'package:flutter_fundamental/provider/restaurant_list_page_provider.dart'; | |
import 'package:flutter_test/flutter_test.dart'; | |
import 'package:mock_web_server/mock_web_server.dart'; | |
import 'constant.dart'; | |
void main() { | |
MockWebServer _server = MockWebServer(); | |
NetworkService _service; | |
setUp(() async { | |
await _server.start(); | |
_service = NetworkService(_server.url); | |
}); | |
tearDown(() { | |
_server.shutdown(); | |
}); | |
group("Getting All Restaurant Testing", () { | |
test("get all restaurant should be success", () async { | |
var expectedValue = JsonHelper().parseRestaurantList(restaurantJson); | |
RestaurantPageProvider pageProvider = | |
RestaurantPageProvider(networkService: _service); | |
_server.defaultResponse = MockResponse() | |
..body = restaurantJson | |
..httpCode = 200; | |
await pageProvider.getRestaurants(); | |
expect(pageProvider.message == null, true); | |
expect(pageProvider.state, RestaurantListPageState.HasData); | |
expect(pageProvider.restaurants.length, expectedValue.length); | |
}); | |
test("get all restaurant should be success but restaurant is empty", | |
() async { | |
var expectedValue = JsonHelper().parseRestaurantList(emptyRestaurantJson); | |
RestaurantPageProvider pageProvider = | |
RestaurantPageProvider(networkService: _service); | |
_server.defaultResponse = MockResponse() | |
..body = emptyRestaurantJson | |
..httpCode = 200; | |
await pageProvider.getRestaurants(); | |
expect(pageProvider.message != null, true); | |
expect( | |
pageProvider.message == | |
"Tidak dapat menampilkan informasi restaurant", | |
true); | |
expect(pageProvider.restaurants.isEmpty, true); | |
expect(pageProvider.restaurants, expectedValue); | |
expect(pageProvider.state, RestaurantListPageState.Empty); | |
}); | |
test("get all restaurant should be fail", () async { | |
RestaurantPageProvider pageProvider = | |
RestaurantPageProvider(networkService: _service); | |
_server.defaultResponse = MockResponse()..httpCode = 404; | |
await pageProvider.getRestaurants(); | |
expect(pageProvider.message != null, true); | |
expect(pageProvider.message, "Terjadi kesalahan saat mendapatkan data"); | |
expect(pageProvider.state, RestaurantListPageState.Error); | |
}); | |
}); | |
group("Search Restaurant By Query Testing", () { | |
test("search restaurant should be success", () async { | |
var expectedValue = JsonHelper().parseRestaurantList(restaurantJson); | |
RestaurantPageProvider pageProvider = | |
RestaurantPageProvider(networkService: _service); | |
_server.defaultResponse = MockResponse() | |
..body = restaurantJson | |
..httpCode = 200; | |
await pageProvider.getRestaurantsByQuery("any"); | |
expect(pageProvider.message, null); | |
expect(pageProvider.state, RestaurantListPageState.HasData); | |
expect(pageProvider.restaurants.length, expectedValue.length); | |
}); | |
test("search restaurant should be success but restaurant is empty", | |
() async { | |
var expectedValue = JsonHelper().parseRestaurantList(emptyRestaurantJson); | |
RestaurantPageProvider pageProvider = | |
RestaurantPageProvider(networkService: _service); | |
_server.defaultResponse = MockResponse() | |
..body = emptyRestaurantJson | |
..httpCode = 200; | |
await pageProvider.getRestaurantsByQuery("any"); | |
expect(pageProvider.message != null, true); | |
expect( | |
pageProvider.message == | |
"Tidak dapat menampilkan informasi restaurant", | |
true); | |
expect(pageProvider.restaurants.isEmpty, true); | |
expect(pageProvider.restaurants, expectedValue); | |
expect(pageProvider.state, RestaurantListPageState.Empty); | |
}); | |
test("search restaurant should be fail", () async { | |
RestaurantPageProvider pageProvider = | |
RestaurantPageProvider(networkService: _service); | |
_server.defaultResponse = MockResponse()..httpCode = 404; | |
await pageProvider.getRestaurants(); | |
expect(pageProvider.message != null, true); | |
expect(pageProvider.message, "Terjadi kesalahan saat mendapatkan data"); | |
expect(pageProvider.state, RestaurantListPageState.Error); | |
}); | |
}); | |
group("Detail Restaurant Testing", () { | |
test("get restaurant detail should be success", () async { | |
var expectedState = | |
JsonHelper().parseRestaurantDetail(detailRestaurantJson); | |
RestaurantDetailPageProvider detailPageProvider = | |
RestaurantDetailPageProvider(_service, "any"); | |
_server.defaultResponse = MockResponse() | |
..body = detailRestaurantJson | |
..httpCode = 200; | |
await detailPageProvider.getRestaurantDetailById(); | |
var actualValue = detailPageProvider.restaurant; | |
expect(detailPageProvider.state, RestaurantDetailPageState.HasData); | |
expect(actualValue.id, expectedState.id); | |
expect(actualValue.name, expectedState.name); | |
expect(actualValue.description, expectedState.description); | |
expect(actualValue.pictureId, expectedState.pictureId); | |
expect(actualValue.city, expectedState.city); | |
expect(actualValue.address, expectedState.address); | |
expect(actualValue.rating, expectedState.rating); | |
}); | |
test("get restaurant detail should be fail", () async { | |
RestaurantDetailPageProvider detailPageProvider = | |
RestaurantDetailPageProvider(_service, "any"); | |
_server.defaultResponse = MockResponse()..httpCode = 404; | |
await detailPageProvider.getRestaurantDetailById(); | |
expect(detailPageProvider.state, RestaurantDetailPageState.Error); | |
expect(detailPageProvider.restaurant == null, true); | |
expect(detailPageProvider.message != null, true); | |
expect(detailPageProvider.message, "Telah terjadi error"); | |
}); | |
}); | |
group("Publishing Restaurant Review Testing", () { | |
test("publishing review should be success", () async { | |
RestaurantDetailPageProvider detailPageProvider = | |
RestaurantDetailPageProvider(_service, "any"); | |
_server.defaultResponse = MockResponse() | |
..body = reviewsJson | |
..httpCode = 200; | |
await detailPageProvider.publishReview("some review", "some id"); | |
expect(detailPageProvider.message, "Berhasil menambahkan review baru"); | |
}); | |
test("publishing review should be success but return error", () async { | |
RestaurantDetailPageProvider detailPageProvider = | |
RestaurantDetailPageProvider(_service, "any"); | |
_server.defaultResponse = MockResponse() | |
..body = errorReviewsJson | |
..httpCode = 200; | |
await detailPageProvider.publishReview("some review", "some id"); | |
expect(detailPageProvider.message, "Gagal menambahkan review baru"); | |
}); | |
test("publishing review should be fail", () async { | |
RestaurantDetailPageProvider detailPageProvider = | |
RestaurantDetailPageProvider(_service, "any"); | |
_server.defaultResponse = MockResponse()..httpCode = 404; | |
await detailPageProvider.publishReview("some review", "some id"); | |
expect(detailPageProvider.message, "Telah terjadi error"); | |
}); | |
}); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter_fundamental/common/constant.dart'; | |
import 'package:flutter_fundamental/model/menu.dart'; | |
import 'package:flutter_fundamental/model/review.dart'; | |
import 'package:flutter_fundamental/provider/restaurant_favorite_provider.dart'; | |
import 'package:flutter_fundamental/ui/component/review_field.dart'; | |
import 'package:provider/provider.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter/widgets.dart'; | |
import 'package:flutter_fundamental/data/network_service.dart'; | |
import 'package:flutter_fundamental/model/restaurant.dart'; | |
import 'package:flutter_fundamental/provider/restaurant_detail_page_provider.dart'; | |
import 'package:flutter_screenutil/flutter_screenutil.dart'; | |
import 'component/error.dart'; | |
import 'component/loading.dart'; | |
class RestaurantDetailPage extends StatefulWidget { | |
static const route = "/restaurant_detail_page"; | |
@override | |
_RestaurantDetailPageState createState() => _RestaurantDetailPageState(); | |
} | |
class _RestaurantDetailPageState extends State<RestaurantDetailPage> | |
with TickerProviderStateMixin { | |
bool _isPageRendered = false; | |
bool _isFavoriteRestaurant = false; | |
AnimationController _animationController; | |
Animation<double> _scaleAnimation; | |
ScrollController _scrollController; | |
@override | |
void initState() { | |
super.initState(); | |
_scrollController = ScrollController(); | |
_scrollController.addListener(() { | |
if (_isPageRendered) { | |
var value = _scrollController.position.pixels; | |
if (value > 300) { | |
_animationController.forward(); | |
} else { | |
_animationController.reverse(); | |
} | |
} | |
}); | |
_animationController = AnimationController( | |
duration: Duration(milliseconds: 50), vsync: this, value: 0.1); | |
_scaleAnimation = | |
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut); | |
_animationController.reverse(); | |
} | |
@override | |
void dispose() { | |
super.dispose(); | |
_scrollController.dispose(); | |
_animationController.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
final Restaurant restaurant = ModalRoute.of(context).settings.arguments; | |
return MultiProvider( | |
providers: [ | |
ChangeNotifierProvider( | |
create: (_) => | |
RestaurantDetailPageProvider(NetworkService(Network.BASE_URL), restaurant.id)), | |
ChangeNotifierProvider( | |
create: (_) => RestaurantFavoritePageProvider( | |
id: restaurant.id, isDetailPage: true), | |
) | |
], | |
child: Scaffold( | |
resizeToAvoidBottomInset: true, | |
floatingActionButton: ScaleTransition( | |
scale: _scaleAnimation, | |
alignment: Alignment.center, | |
child: Consumer<RestaurantDetailPageProvider>( | |
builder: (_, provider, __) { | |
return FloatingActionButton.extended( | |
onPressed: () { | |
showModalBottomSheet( | |
backgroundColor: Colors.transparent, | |
isScrollControlled: true, | |
context: context, | |
builder: (context) { | |
return ReviewFieldComponent( | |
onReviewSubmit: (review) { | |
Navigator.pop(context); | |
if (review.isNotEmpty) | |
provider.publishReview(review, restaurant.id); | |
}, | |
); | |
}, | |
); | |
}, | |
icon: Icon(Icons.sticky_note_2_sharp), | |
label: Text('Tulis Review'), | |
); | |
}, | |
), | |
), | |
body: NestedScrollView( | |
controller: _scrollController, | |
headerSliverBuilder: (context, isScrolled) { | |
return [ | |
SliverAppBar( | |
backgroundColor: Colors.transparent, | |
pinned: true, | |
expandedHeight: 300.h, | |
flexibleSpace: FlexibleSpaceBar( | |
background: Hero( | |
tag: restaurant.id, | |
child: FadeInImage.assetNetwork( | |
placeholder: "assets/image/placeholder.png", | |
image: | |
'https://restaurant-api.dicoding.dev/images/small/${restaurant.pictureId}', | |
fit: BoxFit.cover, | |
), | |
), | |
), | |
actions: [ | |
Consumer<RestaurantFavoritePageProvider>( | |
builder: (_, provider, __) { | |
_isFavoriteRestaurant = provider.isFavoriteRestaurant; | |
return GestureDetector( | |
onTap: () { | |
if(_isFavoriteRestaurant){ | |
provider.doTransaction( | |
restaurant: restaurant, isAddedNewFavorite: false); | |
} else { | |
provider.doTransaction( | |
restaurant: restaurant, isAddedNewFavorite: true); | |
} | |
}, | |
child: Padding( | |
padding: EdgeInsets.only(right: 20), | |
child: Icon( | |
_isFavoriteRestaurant ? Icons.favorite : Icons.favorite_border, | |
color: Colors.redAccent, | |
), | |
), | |
); | |
}, | |
) | |
], | |
), | |
]; | |
}, | |
body: SingleChildScrollView( | |
physics: ScrollPhysics(), | |
child: Container( | |
padding: EdgeInsets.only( | |
top: 20.h, left: 20.w, right: 20.w, bottom: 100.h), | |
child: Consumer<RestaurantDetailPageProvider>( | |
builder: (_, provider, __) { | |
var state = provider.state; | |
switch (state) { | |
case RestaurantDetailPageState.Loading: | |
return LoadingComponent(); | |
case RestaurantDetailPageState.HasData: | |
return _detailContent(provider); | |
case RestaurantDetailPageState.Error: | |
return ErrorComponent( | |
message: "Fail to load data!", | |
showAction: true, | |
onReloadClick: () { | |
provider.getRestaurantDetailById(); | |
}, | |
); | |
default: | |
} | |
return Center(child: Text("Looking for something?")); | |
}, | |
), | |
), | |
), | |
), | |
), | |
); | |
} | |
Widget _detailContent(RestaurantDetailPageProvider provider) { | |
_isPageRendered = true; | |
final restaurant = provider.restaurant; | |
final List<Review> reviews = provider.reviews; | |
return Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
Row( | |
children: [ | |
Text( | |
'${restaurant.name}', | |
style: TextStyle( | |
fontWeight: FontWeight.w300, | |
fontSize: 25.sp, | |
), | |
), | |
Padding(padding: EdgeInsets.all(4.h)), | |
Row( | |
children: [ | |
Icon( | |
Icons.star, | |
size: 16.w, | |
color: Colors.orangeAccent, | |
), | |
Padding(padding: EdgeInsets.all(2.w)), | |
Text( | |
restaurant.rating.toString(), | |
style: TextStyle(fontSize: 14.sp), | |
), | |
], | |
) | |
], | |
), | |
Padding(padding: EdgeInsets.all(6.h)), | |
Text( | |
restaurant.description, | |
style: TextStyle( | |
fontWeight: FontWeight.w400, | |
color: Colors.black54, | |
fontSize: 14.sp, | |
), | |
), | |
Padding(padding: EdgeInsets.all(10.h)), | |
Row( | |
children: [ | |
Icon( | |
Icons.location_on_rounded, | |
size: 16.w, | |
color: Colors.redAccent, | |
), | |
Padding(padding: EdgeInsets.all(2.w)), | |
Text( | |
_fakeLocation('${restaurant.address}${restaurant.city}'), | |
style: TextStyle( | |
fontSize: 14.sp, | |
fontWeight: FontWeight.bold, | |
color: Colors.black45, | |
fontStyle: FontStyle.italic, | |
), | |
), | |
], | |
), | |
Padding(padding: EdgeInsets.all(16.h)), | |
_title("Makanan"), | |
_menuItems(restaurant.menus.foods, "assets/image/food.png"), | |
Padding(padding: EdgeInsets.all(8.h)), | |
_title("Minuman"), | |
_menuItems(restaurant.menus.drinks, "assets/image/drink.png"), | |
Padding(padding: EdgeInsets.all(8.h)), | |
Row( | |
children: [ | |
_title("Review"), | |
Padding(padding: EdgeInsets.all(4.w)), | |
_newReviewCount(provider.newReviewCount), | |
], | |
), | |
ListView.builder( | |
physics: NeverScrollableScrollPhysics(), | |
shrinkWrap: true, | |
itemCount: reviews.length, | |
itemBuilder: (_, index) { | |
var review = reviews[index]; | |
return ListTile( | |
contentPadding: EdgeInsets.only(bottom: 8.w), | |
title: Padding( | |
padding: EdgeInsets.only(bottom: 8.0), | |
child: Text(review.review), | |
), | |
subtitle: Text(review.name), | |
leading: Image.asset("assets/image/profile.png"), | |
); | |
}, | |
) | |
], | |
); | |
} | |
Widget _menuItems(List<MenuItem> data, String leads) => ListView.builder( | |
physics: NeverScrollableScrollPhysics(), | |
shrinkWrap: true, | |
itemCount: data.take(4).toList().length, | |
itemBuilder: (_, index) { | |
var menu = data[index]; | |
return ListTile( | |
contentPadding: EdgeInsets.only(bottom: 8.w), | |
title: Text(menu.name), | |
subtitle: Text(_fakePrice(menu.name)), | |
leading: Image.asset(leads), | |
); | |
}, | |
); | |
Widget _title(String title) => Text( | |
title, | |
style: TextStyle( | |
fontWeight: FontWeight.w400, | |
fontSize: 18.sp, | |
), | |
); | |
Widget _newReviewCount(int count) { | |
if (count > 0) { | |
return Container( | |
decoration: BoxDecoration( | |
color: Colors.redAccent, | |
borderRadius: BorderRadius.all( | |
Radius.circular(20), | |
)), | |
child: Padding( | |
padding: EdgeInsets.all(6.0), | |
child: Text( | |
'+$count', | |
style: TextStyle( | |
color: Colors.white, | |
fontWeight: FontWeight.normal, | |
), | |
), | |
), | |
); | |
} else { | |
return Text(''); | |
} | |
} | |
String _fakePrice(String name) { | |
return 'Rp. ${name.length},000'; | |
} | |
String _fakeLocation(String address) { | |
return '${address.length / 100} KM dari lokasi kamu sekarang'; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
RestaurantFavoritePageProvider({String id, bool isDetailPage}) { | |
_databaseHelper = DatabaseHelper(); | |
loadFavoriteRestaurant(); | |
if (isDetailPage) { | |
checkIfRestaurantIsFavorite(id); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment