Skip to content

Commit 799ce7f

Browse files
[Compass App] Cleanup and extra tests (#2437)
Some changes to make the project ready to merge to main - Fix formatting. - Add project to the `stable` channel CI (potentially could be added for `beta` and `master`) - CI only runs when targeting `main`, so I run the command locally and it worked. - Add unit tests for the `ApiClient` and `AuthApiClient` which had no test coverage outside of integration tests. - Resolved some TODOs I left before. ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
1 parent b00ce6c commit 799ce7f

File tree

13 files changed

+210
-50
lines changed

13 files changed

+210
-50
lines changed

compass_app/app/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,6 @@ app.*.map.json
4141
/android/app/debug
4242
/android/app/profile
4343
/android/app/release
44+
45+
# Coverage test report
46+
coverage/

compass_app/app/lib/data/repositories/destination/destination_repository.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,4 @@ import '../../../utils/result.dart';
55
abstract class DestinationRepository {
66
/// Get complete list of destinations
77
Future<Result<List<Destination>>> getDestinations();
8-
9-
// TODO: Consider creating getByContinent instead of filtering in ViewModel
108
}

compass_app/app/lib/data/services/api/api_client.dart

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,18 @@ import 'model/user/user_api_model.dart';
1111
/// Adds the `Authentication` header to a header configuration.
1212
typedef AuthHeaderProvider = String? Function();
1313

14-
// TODO: Configurable baseurl/host/port
1514
class ApiClient {
16-
ApiClient();
15+
ApiClient({
16+
String? host,
17+
int? port,
18+
HttpClient Function()? clientFactory,
19+
}) : _host = host ?? 'localhost',
20+
_port = port ?? 8080,
21+
_clientFactory = clientFactory ?? (() => HttpClient());
22+
23+
final String _host;
24+
final int _port;
25+
final HttpClient Function() _clientFactory;
1726

1827
AuthHeaderProvider? _authHeaderProvider;
1928

@@ -29,9 +38,9 @@ class ApiClient {
2938
}
3039

3140
Future<Result<List<Continent>>> getContinents() async {
32-
final client = HttpClient();
41+
final client = _clientFactory();
3342
try {
34-
final request = await client.get('localhost', 8080, '/continent');
43+
final request = await client.get(_host, _port, '/continent');
3544
await _authHeader(request.headers);
3645
final response = await request.close();
3746
if (response.statusCode == 200) {
@@ -50,9 +59,9 @@ class ApiClient {
5059
}
5160

5261
Future<Result<List<Destination>>> getDestinations() async {
53-
final client = HttpClient();
62+
final client = _clientFactory();
5463
try {
55-
final request = await client.get('localhost', 8080, '/destination');
64+
final request = await client.get(_host, _port, '/destination');
5665
await _authHeader(request.headers);
5766
final response = await request.close();
5867
if (response.statusCode == 200) {
@@ -71,10 +80,10 @@ class ApiClient {
7180
}
7281

7382
Future<Result<List<Activity>>> getActivityByDestination(String ref) async {
74-
final client = HttpClient();
83+
final client = _clientFactory();
7584
try {
7685
final request =
77-
await client.get('localhost', 8080, '/destination/$ref/activity');
86+
await client.get(_host, _port, '/destination/$ref/activity');
7887
await _authHeader(request.headers);
7988
final response = await request.close();
8089
if (response.statusCode == 200) {
@@ -94,9 +103,9 @@ class ApiClient {
94103
}
95104

96105
Future<Result<List<BookingApiModel>>> getBookings() async {
97-
final client = HttpClient();
106+
final client = _clientFactory();
98107
try {
99-
final request = await client.get('localhost', 8080, '/booking');
108+
final request = await client.get(_host, _port, '/booking');
100109
await _authHeader(request.headers);
101110
final response = await request.close();
102111
if (response.statusCode == 200) {
@@ -116,9 +125,9 @@ class ApiClient {
116125
}
117126

118127
Future<Result<BookingApiModel>> getBooking(int id) async {
119-
final client = HttpClient();
128+
final client = _clientFactory();
120129
try {
121-
final request = await client.get('localhost', 8080, '/booking/$id');
130+
final request = await client.get(_host, _port, '/booking/$id');
122131
await _authHeader(request.headers);
123132
final response = await request.close();
124133
if (response.statusCode == 200) {
@@ -136,9 +145,9 @@ class ApiClient {
136145
}
137146

138147
Future<Result<BookingApiModel>> postBooking(BookingApiModel booking) async {
139-
final client = HttpClient();
148+
final client = _clientFactory();
140149
try {
141-
final request = await client.post('localhost', 8080, '/booking');
150+
final request = await client.post(_host, _port, '/booking');
142151
await _authHeader(request.headers);
143152
request.write(jsonEncode(booking));
144153
final response = await request.close();
@@ -157,9 +166,9 @@ class ApiClient {
157166
}
158167

159168
Future<Result<UserApiModel>> getUser() async {
160-
final client = HttpClient();
169+
final client = _clientFactory();
161170
try {
162-
final request = await client.get('localhost', 8080, '/user');
171+
final request = await client.get(_host, _port, '/user');
163172
await _authHeader(request.headers);
164173
final response = await request.close();
165174
if (response.statusCode == 200) {

compass_app/app/lib/data/services/api/auth_api_client.dart

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,23 @@ import '../../../utils/result.dart';
55
import 'model/login_request/login_request.dart';
66
import 'model/login_response/login_response.dart';
77

8-
// TODO: Configurable baseurl/host/port
98
class AuthApiClient {
9+
AuthApiClient({
10+
String? host,
11+
int? port,
12+
HttpClient Function()? clientFactory,
13+
}) : _host = host ?? 'localhost',
14+
_port = port ?? 8080,
15+
_clientFactory = clientFactory ?? (() => HttpClient());
16+
17+
final String _host;
18+
final int _port;
19+
final HttpClient Function() _clientFactory;
20+
1021
Future<Result<LoginResponse>> login(LoginRequest loginRequest) async {
11-
final client = HttpClient();
22+
final client = _clientFactory();
1223
try {
13-
final request = await client.post('localhost', 8080, '/login');
24+
final request = await client.post(_host, _port, '/login');
1425
request.write(jsonEncode(loginRequest));
1526
final response = await request.close();
1627
if (response.statusCode == 200) {

compass_app/app/lib/ui/core/themes/text_styles.dart

Lines changed: 0 additions & 24 deletions
This file was deleted.

compass_app/app/lib/ui/home/widgets/home_screen.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ class HomeScreen extends StatelessWidget {
8484
}
8585
}
8686

87-
8887
class _Booking extends StatelessWidget {
8988
const _Booking({
9089
super.key,

compass_app/app/lib/ui/results/widgets/result_card.dart

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import 'package:cached_network_image/cached_network_image.dart';
22
import 'package:flutter/material.dart';
3+
import 'package:google_fonts/google_fonts.dart';
34
import '../../../domain/models/destination/destination.dart';
45
import '../../../utils/image_error_listener.dart';
5-
import '../../core/themes/text_styles.dart';
66
import '../../core/ui/tag_chip.dart';
77

88
class ResultCard extends StatelessWidget {
@@ -38,7 +38,7 @@ class ResultCard extends StatelessWidget {
3838
children: [
3939
Text(
4040
destination.name.toUpperCase(),
41-
style: TextStyles.cardTitleStyle,
41+
style: _cardTitleStyle,
4242
),
4343
const SizedBox(
4444
height: 6,
@@ -67,3 +67,19 @@ class ResultCard extends StatelessWidget {
6767
);
6868
}
6969
}
70+
71+
final _cardTitleStyle = GoogleFonts.rubik(
72+
textStyle: const TextStyle(
73+
fontWeight: FontWeight.w800,
74+
fontSize: 15.0,
75+
color: Colors.white,
76+
letterSpacing: 1,
77+
shadows: [
78+
// Helps to read the text a bit better
79+
Shadow(
80+
blurRadius: 3.0,
81+
color: Colors.black,
82+
)
83+
],
84+
),
85+
);
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import 'package:compass_app/data/services/api/api_client.dart';
2+
import 'package:compass_app/domain/models/continent/continent.dart';
3+
import 'package:flutter_test/flutter_test.dart';
4+
5+
import '../../../../testing/mocks.dart';
6+
import '../../../../testing/models/activity.dart';
7+
import '../../../../testing/models/booking.dart';
8+
import '../../../../testing/models/destination.dart';
9+
import '../../../../testing/models/user.dart';
10+
11+
void main() {
12+
group('ApiClient', () {
13+
late MockHttpClient mockHttpClient;
14+
late ApiClient apiClient;
15+
16+
setUp(() {
17+
mockHttpClient = MockHttpClient();
18+
apiClient = ApiClient(clientFactory: () => mockHttpClient);
19+
});
20+
21+
test('should get continents', () async {
22+
final continents = [const Continent(name: 'NAME', imageUrl: 'URL')];
23+
mockHttpClient.mockGet('/continent', continents);
24+
final result = await apiClient.getContinents();
25+
expect(result.asOk.value, continents);
26+
});
27+
28+
test('should get activities by destination', () async {
29+
final activites = [kActivity];
30+
mockHttpClient.mockGet(
31+
'/destination/${kDestination1.ref}/activity',
32+
activites,
33+
);
34+
final result =
35+
await apiClient.getActivityByDestination(kDestination1.ref);
36+
expect(result.asOk.value, activites);
37+
});
38+
39+
test('should get booking', () async {
40+
mockHttpClient.mockGet(
41+
'/booking/${kBookingApiModel.id}',
42+
kBookingApiModel,
43+
);
44+
final result = await apiClient.getBooking(kBookingApiModel.id!);
45+
expect(result.asOk.value, kBookingApiModel);
46+
});
47+
48+
test('should get bookings', () async {
49+
mockHttpClient.mockGet('/booking', [kBookingApiModel]);
50+
final result = await apiClient.getBookings();
51+
expect(result.asOk.value, [kBookingApiModel]);
52+
});
53+
54+
test('should get destinations', () async {
55+
mockHttpClient.mockGet('/destination', [kDestination1]);
56+
final result = await apiClient.getDestinations();
57+
expect(result.asOk.value, [kDestination1]);
58+
});
59+
60+
test('should get user', () async {
61+
mockHttpClient.mockGet('/user', userApiModel);
62+
final result = await apiClient.getUser();
63+
expect(result.asOk.value, userApiModel);
64+
});
65+
66+
test('should post booking', () async {
67+
mockHttpClient.mockPost('/booking', kBookingApiModel);
68+
final result = await apiClient.postBooking(kBookingApiModel);
69+
expect(result.asOk.value, kBookingApiModel);
70+
});
71+
});
72+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import 'package:compass_app/data/services/api/auth_api_client.dart';
2+
import 'package:compass_app/data/services/api/model/login_request/login_request.dart';
3+
import 'package:compass_app/data/services/api/model/login_response/login_response.dart';
4+
import 'package:flutter_test/flutter_test.dart';
5+
6+
import '../../../../testing/mocks.dart';
7+
8+
void main() {
9+
group('AuthApiClient', () {
10+
late MockHttpClient mockHttpClient;
11+
late AuthApiClient apiClient;
12+
13+
setUp(() {
14+
mockHttpClient = MockHttpClient();
15+
apiClient = AuthApiClient(clientFactory: () => mockHttpClient);
16+
});
17+
18+
test('should post login', () async {
19+
const loginResponse = LoginResponse(
20+
token: 'TOKEN',
21+
userId: '123',
22+
);
23+
mockHttpClient.mockPost(
24+
'/login',
25+
loginResponse,
26+
200,
27+
);
28+
final result = await apiClient.login(
29+
const LoginRequest(
30+
email: 'EMAIL',
31+
password: 'PASSWORD',
32+
),
33+
);
34+
expect(result.asOk.value, loginResponse);
35+
});
36+
});
37+
}

compass_app/app/test/ui/results/results_viewmodel_test.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ void main() {
2121

2222
// perform a simple test
2323
// verifies that the list of items is properly loaded
24-
// TODO: Verify loading state and calls to search method
2524
test('should load items', () async {
2625
expect(viewModel.destinations.length, 2);
2726
});

0 commit comments

Comments
 (0)