Skip to content

Commit 6450b5d

Browse files
authored
Merge pull request #42 from ashtanko/feature/cores
Implement cores
2 parents 897fdcf + 5dd4198 commit 6450b5d

File tree

81 files changed

+6421
-496
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+6421
-496
lines changed

codecov.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ coverage:
33
status:
44
project:
55
default:
6-
target: 59%
6+
target: 50%
77
threshold: 1%
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_bloc_app_template/main.dart' as app;
3+
import 'package:flutter_test/flutter_test.dart';
4+
import 'package:integration_test/integration_test.dart';
5+
6+
void main() {
7+
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
8+
9+
setUp(() {});
10+
11+
testWidgets('cores test', (
12+
WidgetTester tester,
13+
) async {
14+
app.main([]);
15+
await tester.pumpAndSettle();
16+
17+
expect(find.byKey(const Key('cores_screen')), findsOneWidget);
18+
19+
final coresTab = find.byKey(const Key('cores_screen'));
20+
await tester.ensureVisible(coresTab);
21+
await tester.tap(coresTab);
22+
await tester.pumpAndSettle();
23+
24+
final firstItem = find.byKey(const Key('Merlin1A0'));
25+
26+
expect(firstItem, findsOneWidget);
27+
await tester.ensureVisible(firstItem);
28+
29+
final allFilterChip = find.byKey(const Key('core_status_filter_all'));
30+
await tester.ensureVisible(allFilterChip);
31+
32+
await tester.tap(allFilterChip);
33+
await tester.pumpAndSettle();
34+
35+
expect(find.text('Merlin1A'), findsOneWidget);
36+
37+
final activeFilterChip = find.byKey(const Key('core_status_filter_active'));
38+
await tester.ensureVisible(activeFilterChip);
39+
40+
await tester.tap(activeFilterChip);
41+
await tester.pumpAndSettle();
42+
43+
expect(find.text('active'), findsAtLeast(1));
44+
45+
final lostFilterChip = find.byKey(const Key('core_status_filter_lost'));
46+
await tester.ensureVisible(lostFilterChip);
47+
48+
await tester.tap(lostFilterChip);
49+
await tester.pumpAndSettle();
50+
51+
expect(find.text('lost'), findsAtLeast(1));
52+
53+
final inactiveFilterChip = find.byKey(
54+
const Key('core_status_filter_inactive'),
55+
);
56+
await tester.ensureVisible(inactiveFilterChip);
57+
58+
await tester.tap(inactiveFilterChip);
59+
await tester.pumpAndSettle();
60+
61+
expect(find.text('inactive'), findsAtLeast(1));
62+
63+
final unknownFilterChip = find.byKey(
64+
const Key('core_status_filter_unknown'),
65+
);
66+
await tester.ensureVisible(unknownFilterChip);
67+
68+
await tester.tap(unknownFilterChip);
69+
await tester.pumpAndSettle();
70+
71+
expect(find.text('unknown'), findsAtLeast(1));
72+
73+
final searchField = find.byType(TextField);
74+
expect(searchField, findsOneWidget);
75+
76+
await tester.enterText(searchField, 'flutter');
77+
await tester.pumpAndSettle();
78+
79+
expect(find.text('No cores found for "flutter"'), findsOneWidget);
80+
81+
await tester.tap(allFilterChip);
82+
await tester.pumpAndSettle();
83+
84+
await tester.enterText(searchField, 'Merlin');
85+
await tester.pumpAndSettle();
86+
87+
expect(find.text('Merlin'), findsAtLeast(1));
88+
89+
// await tester.tap(find.byType(CoreItemWidget).first);
90+
// await tester.pumpAndSettle();
91+
// todo replace with details screen text
92+
// expect(find.text('Merlin1A'), findsOneWidget);
93+
});
94+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import 'package:bloc_test/bloc_test.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter_bloc_app_template/features/cores/bloc/cores_bloc.dart';
4+
import 'package:flutter_bloc_app_template/features/cores/cores_screen.dart';
5+
import 'package:flutter_bloc_app_template/features/cores/widget/core_loading_content.dart';
6+
import 'package:flutter_bloc_app_template/features/cores/widget/cores_empty_widget.dart';
7+
import 'package:flutter_bloc_app_template/features/cores/widget/cores_error_widget.dart';
8+
import 'package:flutter_bloc_app_template/features/cores/widget/cores_not_found_widget.dart';
9+
import 'package:flutter_bloc_app_template/models/core/core_resource.dart';
10+
import 'package:flutter_test/flutter_test.dart';
11+
import 'package:integration_test/integration_test.dart';
12+
import 'package:mocktail/mocktail.dart';
13+
14+
import '../test/bloc/utils.dart';
15+
16+
class MockCoresBloc extends MockBloc<CoresEvent, CoresState>
17+
implements CoresBloc {}
18+
19+
void main() {
20+
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
21+
22+
late MockCoresBloc mockBloc;
23+
24+
setUp(() {
25+
mockBloc = MockCoresBloc();
26+
});
27+
28+
testWidgets('renders LoadingContent when state is CoresLoadingState',
29+
(tester) async {
30+
when(() => mockBloc.state).thenReturn(const CoresLoadingState());
31+
32+
await tester.pumpLocalizedWidgetWithBloc<CoresBloc>(
33+
bloc: mockBloc,
34+
child: const CustomScrollView(
35+
slivers: [CoresBlocContent()],
36+
),
37+
locale: const Locale('en'),
38+
);
39+
await tester.pump();
40+
41+
expect(find.byType(CoreLoadingContent), findsOneWidget);
42+
});
43+
44+
testWidgets('renders CoresListWidget when state is CoresSuccessState',
45+
(tester) async {
46+
final cores = <CoreResource>[
47+
const CoreResource(
48+
coreSerial: 'B1051',
49+
missions: [MissionResource(name: null, flight: 1)],
50+
status: 'active',
51+
),
52+
]; // provide mock cores
53+
when(() => mockBloc.state)
54+
.thenReturn(CoresSuccessState(filteredCores: cores));
55+
56+
await tester.pumpLocalizedWidgetWithBloc<CoresBloc>(
57+
bloc: mockBloc,
58+
child: const CustomScrollView(
59+
slivers: [CoresBlocContent()],
60+
),
61+
locale: const Locale('en'),
62+
);
63+
await tester.pumpAndSettle();
64+
65+
expect(find.byType(CoresListWidget), findsOneWidget);
66+
});
67+
68+
testWidgets('renders CoresErrorWidget when state is CoresErrorState',
69+
(tester) async {
70+
const errorMessage = 'Error occurred';
71+
when(() => mockBloc.state).thenReturn(const CoresErrorState(errorMessage));
72+
73+
await tester.pumpLocalizedWidgetWithBloc<CoresBloc>(
74+
bloc: mockBloc,
75+
child: const CustomScrollView(
76+
slivers: [CoresBlocContent()],
77+
),
78+
locale: const Locale('en'),
79+
);
80+
await tester.pumpAndSettle();
81+
82+
expect(find.byType(CoresErrorWidget), findsOneWidget);
83+
//expect(find.text(errorMessage), findsOneWidget);
84+
});
85+
86+
testWidgets('renders CoresEmptyWidget when state is CoresEmptyState',
87+
(tester) async {
88+
when(() => mockBloc.state).thenReturn(const CoresEmptyState());
89+
90+
await tester.pumpLocalizedWidgetWithBloc<CoresBloc>(
91+
bloc: mockBloc,
92+
child: const CustomScrollView(
93+
slivers: [CoresBlocContent()],
94+
),
95+
locale: const Locale('en'),
96+
);
97+
await tester.pumpAndSettle();
98+
99+
expect(find.byType(CoresEmptyWidget), findsOneWidget);
100+
});
101+
102+
testWidgets('renders CoresNotFoundWidget when state is CoresNotFoundState',
103+
(tester) async {
104+
const query = 'Falcon';
105+
when(() => mockBloc.state)
106+
.thenReturn(const CoresNotFoundState(searchQuery: query));
107+
108+
await tester.pumpLocalizedWidgetWithBloc<CoresBloc>(
109+
bloc: mockBloc,
110+
child: const CustomScrollView(
111+
slivers: [CoresBlocContent()],
112+
),
113+
locale: const Locale('en'),
114+
);
115+
await tester.pumpAndSettle();
116+
117+
expect(find.byType(CoresNotFoundWidget), findsOneWidget);
118+
expect(find.textContaining(query), findsOneWidget);
119+
});
120+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import 'package:flutter_bloc_app_template/data/network/api_result.dart';
2+
import 'package:flutter_bloc_app_template/data/network/model/core/network_core_model.dart';
3+
import 'package:flutter_bloc_app_template/data/network/service/cores/cores_service.dart';
4+
5+
abstract class CoresDataSource {
6+
Future<ApiResult<List<NetworkCoreModel>>> getCores({
7+
bool? hasId = true,
8+
int? limit,
9+
int? offset,
10+
});
11+
12+
Future<ApiResult<NetworkCoreModel>> getCore(String coreSerial);
13+
}
14+
15+
class CoresNetworkDataSource implements CoresDataSource {
16+
CoresNetworkDataSource(this._service);
17+
18+
final CoresService _service;
19+
20+
@override
21+
Future<ApiResult<List<NetworkCoreModel>>> getCores({
22+
bool? hasId = true,
23+
int? limit,
24+
int? offset,
25+
}) async {
26+
try {
27+
final list = await _service.fetchCores(
28+
hasId: hasId,
29+
limit: limit,
30+
offset: offset,
31+
);
32+
33+
return ApiResult.success(list);
34+
} catch (e) {
35+
return Future.value(ApiResult.error(e.toString()));
36+
}
37+
}
38+
39+
@override
40+
Future<ApiResult<NetworkCoreModel>> getCore(String coreSerial) async {
41+
try {
42+
final result = await _service.fetchCore(coreSerial);
43+
return ApiResult.success(result);
44+
} catch (e) {
45+
return Future.value(ApiResult.error(e.toString()));
46+
}
47+
}
48+
}

lib/data/network/model/core/network_core_model.dart

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,35 @@ part 'network_core_model.g.dart';
77
abstract class NetworkCoreModel with _$NetworkCoreModel {
88
const factory NetworkCoreModel({
99
@JsonKey(name: 'core_serial') String? coreSerial,
10-
int? flight,
1110
int? block,
12-
bool? gridfins,
13-
bool? legs,
14-
bool? reused,
15-
@JsonKey(name: 'land_success') bool? landSuccess,
16-
@JsonKey(name: 'landing_intent') bool? landingIntent,
17-
@JsonKey(name: 'landing_type') String? landingType,
18-
@JsonKey(name: 'landing_vehicle') String? landingVehicle,
11+
String? status,
12+
@JsonKey(name: 'original_launch') String? originalLaunch,
13+
@JsonKey(name: 'original_launch_unix') int? originalLaunchUnix,
14+
List<NetworkMission>? missions,
15+
@JsonKey(name: 'reuse_count') int? reuseCount,
16+
@JsonKey(name: 'rtls_attempts') int? rtlsAttempts,
17+
@JsonKey(name: 'rtls_landings') int? rtlsLandings,
18+
@JsonKey(name: 'asds_attempts') int? asdsAttempts,
19+
@JsonKey(name: 'asds_landings') int? asdsLandings,
20+
@JsonKey(name: 'water_landing') bool? waterLanding,
21+
String? details,
1922
}) = _NetworkCoreModel;
2023

2124
const NetworkCoreModel._();
2225

2326
factory NetworkCoreModel.fromJson(Map<String, dynamic> json) =>
2427
_$NetworkCoreModelFromJson(json);
2528
}
29+
30+
@freezed
31+
abstract class NetworkMission with _$NetworkMission {
32+
const factory NetworkMission({
33+
String? name,
34+
int? flight,
35+
}) = _NetworkMission;
36+
37+
const NetworkMission._();
38+
39+
factory NetworkMission.fromJson(Map<String, dynamic> json) =>
40+
_$NetworkMissionFromJson(json);
41+
}

0 commit comments

Comments
 (0)