Skip to content
This repository was archived by the owner on Sep 14, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'package:flutter/services.dart';
import 'package:scoped_model/scoped_model.dart';

import 'models/current_user_model.dart';
import 'models/post.dart';
import 'models/post_mock.dart';
import 'pages/home_page.dart';
import 'pages/register_page.dart';
import 'theme.dart';
Expand All @@ -22,12 +24,27 @@ class MyApp extends StatelessWidget {
debugShowCheckedModeBanner: false,
title: 'Birb',
theme: buildThemeData(),
home: const HomePage(title: 'Birb'),
home: HomePage(
title: 'Birb',
posts: _loadPosts(context),
),
routes: <String, WidgetBuilder>{
RegisterPage.routeName: (BuildContext context) =>
const RegisterPage(),
},
),
);
}

Stream<List<Post>> _loadPosts(BuildContext context) {
final List<List<dynamic>> mockSnapshot = <List<dynamic>>[
List<dynamic>.generate(10, (int index) => mockPostData(index: index))
];
return Stream<List<dynamic>>.fromIterable(mockSnapshot)
.map(_convertToPosts);
}

List<Post> _convertToPosts(List<dynamic> data) {
return data.map((dynamic item) => Post.fromMap(item)).toList();
}
}
22 changes: 7 additions & 15 deletions app/lib/pages/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@ import 'package:scoped_model/scoped_model.dart';

import '../models/current_user_model.dart';
import '../models/post.dart';
import '../models/post_mock.dart';
import '../posts_list.dart';
import '../sign_in_fab.dart';
import '../sign_out_action.dart';

class HomePage extends StatefulWidget {
const HomePage({Key key, this.title}) : super(key: key);
const HomePage({
Key key,
@required this.title,
@required this.posts,
}) : super(key: key);

static const String routeName = '/';
final String title;
final Stream<List<Post>> posts;

@override
_HomePageState createState() => _HomePageState();
Expand All @@ -30,7 +34,7 @@ class _HomePageState extends State<HomePage> {
SignOutAction(),
],
),
body: PostsList(_loadPosts(context)),
body: PostsList(widget.posts),
floatingActionButton: _floatingActionButton(),
);
}
Expand All @@ -45,16 +49,4 @@ class _HomePageState extends State<HomePage> {
model.user == null ? const SignInFab() : Container(),
);
}

Stream<List<Post>> _loadPosts(BuildContext context) {
final List<List<dynamic>> mockSnapshot = <List<dynamic>>[
List<dynamic>.generate(10, (int index) => mockPostData(index: index))
];
return Stream<List<dynamic>>.fromIterable(mockSnapshot)
.map(_convertToPosts);
}

List<Post> _convertToPosts(List<dynamic> data) {
return data.map((dynamic item) => Post.fromMap(item)).toList();
}
}
30 changes: 30 additions & 0 deletions app/lib/pages/post_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';

import '../models/post.dart';
import '../post_item.dart';

class PostPage extends StatelessWidget {
const PostPage({
Key key,
@required this.post,
}) : super(key: key);

final Post post;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Post'),
centerTitle: true,
elevation: 0.0,
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 8.0),
child: PostItem(post),
),
),
);
}
}
9 changes: 6 additions & 3 deletions app/lib/post_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ class PostItem extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
ClipRRect(
child: Image.network(post.imageUrl),
borderRadius: BorderRadius.circular(10.0),
Hero(
tag: post.id,
child: ClipRRect(
child: Image.network(post.imageUrl),
borderRadius: BorderRadius.circular(10.0),
),
),
const SizedBox(height: 8.0),
Text(
Expand Down
35 changes: 32 additions & 3 deletions app/lib/posts_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';

import 'models/post.dart';
import 'no_content.dart';
import 'pages/post_page.dart';
import 'post_item.dart';

class PostsList extends StatelessWidget {
Expand All @@ -25,20 +26,48 @@ class PostsList extends StatelessWidget {
if (snapshot.data.isEmpty) {
return const NoContent();
}
return _itemList(snapshot.data);
return _itemList(context, snapshot.data);
}
},
);
}

ListView _itemList(List<Post> items) {
ListView _itemList(BuildContext context, List<Post> items) {
return ListView(
children: items.map((Post post) {
return Container(
padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 8.0),
child: PostItem(post),
child: InkWell(
onTap: () => _navigateToPost(context, post),
child: PostItem(post),
),
);
}).toList(),
);
}

void _navigateToPost(BuildContext context, Post post) {
Navigator.of(context).push(
PageRouteBuilder<PostPage>(
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return PostPage(post: post);
},
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return FadeTransition(
opacity: animation,
child: child,
);
},
),
);
}
}
20 changes: 14 additions & 6 deletions app/test/pages/home_page_test.dart
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
import 'package:birb/models/current_user_model.dart';
import 'package:birb/models/post_mock.dart';
import 'package:birb/pages/home_page.dart';
import 'package:birb/posts_list.dart';
import 'package:birb/sign_in_fab.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:scoped_model/scoped_model.dart';

import '../mocks/app_mock.dart';
import '../mocks/current_user_model_mock.dart';

void main() {
final dynamic app = appMock(
child: const HomePage(title: 'Awesome'),
mock: CurrentUserModelMock(),
);
ScopedModel<CurrentUserModel> app({int count}) {
return appMock(
child: HomePage(
title: 'Awesome',
posts: mockPosts(count: 5),
),
mock: CurrentUserModelMock(),
);
}

testWidgets('Renders list of posts', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(app);
await tester.pumpWidget(app());

expect(find.text('Awesome'), findsOneWidget);
expect(find.byType(PostsList), findsOneWidget);
});

testWidgets('Renders sign in FAB', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(app);
await tester.pumpWidget(app());

expect(find.byType(SignInFab), findsOneWidget);
});
Expand Down
30 changes: 30 additions & 0 deletions app/test/pages/post_page_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'package:birb/models/current_user_model.dart';
import 'package:birb/models/post_mock.dart';
import 'package:birb/pages/post_page.dart';
import 'package:birb/post_item.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:image_test_utils/image_test_utils.dart';
import 'package:scoped_model/scoped_model.dart';

import '../mocks/app_mock.dart';
import '../mocks/current_user_model_mock.dart';

void main() {
ScopedModel<CurrentUserModel> app({int count}) {
return appMock(
child: PostPage(
post: mockPost(),
),
mock: CurrentUserModelMock(),
);
}

testWidgets('Renders a post', (WidgetTester tester) async {
provideMockedNetworkImages(() async {
await tester.pumpWidget(app());

expect(find.text('Post'), findsOneWidget);
expect(find.byType(PostItem), findsOneWidget);
});
});
}
5 changes: 4 additions & 1 deletion app/test/post_item_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ void main() {
testWidgets('Renders a post', (WidgetTester tester) async {
provideMockedNetworkImages(() async {
final Post post = mockPost();
// Build our app and trigger a frame.
await tester.pumpWidget(MaterialApp(
home: PostItem(post),
));
final Finder hero = find.byType(Hero);

expect(hero, findsOneWidget);
expect(find.byType(ClipRRect), findsOneWidget);
expect(find.byType(Image), findsOneWidget);
expect(tester.widget<Hero>(hero).tag, post.id);
expect(find.text(post.username), findsOneWidget);
expect(find.text(post.text), findsOneWidget);
});
Expand Down
47 changes: 34 additions & 13 deletions app/test/posts_list_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,64 @@ import 'package:birb/models/post_mock.dart';
import 'package:birb/no_content.dart';
import 'package:birb/post_item.dart';
import 'package:birb/posts_list.dart';
import 'package:birb/pages/post_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:image_test_utils/image_test_utils.dart';
import 'package:birb/models/current_user_model.dart';
import 'package:scoped_model/scoped_model.dart';

import 'mocks/app_mock.dart';
import 'mocks/current_user_model_mock.dart';

void main() {
ScopedModel<CurrentUserModel> app({int count}) {
return appMock(
child: PostsList(mockPosts(count: count)),
mock: CurrentUserModelMock(),
);
}

group('PostsList', () {
testWidgets('renders list of PostItems', (WidgetTester tester) async {
provideMockedNetworkImages(() async {
// Build our app and trigger a frame.
await tester.pumpWidget(MaterialApp(
home: PostsList(mockPosts(count: 5)),
));
await tester.pumpWidget(app(count: 5));

expect(find.text('Loading...'), findsOneWidget);

await tester.pump(Duration.zero);
await tester.pumpAndSettle();

expect(find.byType(PostItem), findsNWidgets(5));
});
});

testWidgets('Opens post page when item is tapped',
(WidgetTester tester) async {
provideMockedNetworkImages(() async {
// TODO(abraham): get the post ID and check for Hero tag before/after
await tester.pumpWidget(app(count: 5));
await tester.pumpAndSettle();
await tester.tap(find.byType(PostItem).first);
await tester.pumpAndSettle();

expect(find.byType(PostPage), findsOneWidget);
});
});

testWidgets('renders NoContent widget', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MaterialApp(
home: PostsList(mockPosts(count: 0)),
));
await tester.pump(Duration.zero);
await tester.pumpWidget(app(count: 0));
await tester.pumpAndSettle();

expect(find.byType(NoContent), findsOneWidget);
});

testWidgets('renders error text', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MaterialApp(
home: PostsList(Future<List<Post>>.error('Bad Connection').asStream()),
home: PostsList(
Future<List<Post>>.error('Bad Connection').asStream(),
),
));
await tester.pump(Duration.zero);
await tester.pumpAndSettle();

expect(find.text('Error: Bad Connection'), findsOneWidget);
});
Expand Down