Skip to content

Commit

Permalink
Merge pull request #23 from mqkotoo/session/10
Browse files Browse the repository at this point in the history
Session/10
  • Loading branch information
mqkotoo authored Oct 18, 2023
2 parents 4e41359 + 018b137 commit 3ac3c70
Show file tree
Hide file tree
Showing 12 changed files with 468 additions and 57 deletions.
3 changes: 3 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@ analyzer:
errors:
# riverpod_generatorの影響でg.dartの末尾に不要な指定がつくので記載
duplicate_ignore: ignore
# @visibleForTestingアノテーションがついたメンバが参照されるとエラーにする
invalid_use_of_visible_for_testing_member: error

plugins:
- custom_lint
14 changes: 11 additions & 3 deletions lib/model/weather_condition.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_svg/svg.dart';
import 'package:flutter_training/gen/assets.gen.dart';

Expand All @@ -8,12 +9,19 @@ enum WeatherCondition {
}

extension WeatherSvgImage on WeatherCondition {
@visibleForTesting
static const sunnyLabel = 'Sunny image';
@visibleForTesting
static const cloudyLabel = 'Cloudy image';
@visibleForTesting
static const rainyLabel = 'Rainy image';

SvgPicture get svgImage => switch (this) {
WeatherCondition.sunny =>
Assets.images.sunny.svg(semanticsLabel: 'Sunny image'),
Assets.images.sunny.svg(semanticsLabel: sunnyLabel),
WeatherCondition.cloudy =>
Assets.images.cloudy.svg(semanticsLabel: 'Cloudy image'),
Assets.images.cloudy.svg(semanticsLabel: cloudyLabel),
WeatherCondition.rainy =>
Assets.images.rainy.svg(semanticsLabel: 'Rainy image')
Assets.images.rainy.svg(semanticsLabel: rainyLabel)
};
}
13 changes: 9 additions & 4 deletions lib/service/weather_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:flutter_training/model/weather_forecast.dart';
import 'package:flutter_training/model/weather_request.dart';
import 'package:flutter_training/utils/api/result.dart';
import 'package:flutter_training/utils/error/error_message.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:yumemi_weather/yumemi_weather.dart';
Expand Down Expand Up @@ -39,14 +40,18 @@ class WeatherService {
} on YumemiWeatherError catch (e) {
return switch (e) {
YumemiWeatherError.invalidParameter =>
const Failure<WeatherForecast, String>('パラメータが有効ではありません。'),
const Failure<WeatherForecast, String>(ErrorMessage.invalidParameter),
YumemiWeatherError.unknown =>
const Failure<WeatherForecast, String>('予期せぬエラーが発生しました。')
const Failure<WeatherForecast, String>(ErrorMessage.unknown)
};
} on CheckedFromJsonException catch (_) {
return const Failure<WeatherForecast, String>('不適切なデータを取得しました。');
return const Failure<WeatherForecast, String>(
ErrorMessage.receiveInvalidData,
);
} on FormatException catch (_) {
return const Failure<WeatherForecast, String>('不適切なデータを取得しました。');
return const Failure<WeatherForecast, String>(
ErrorMessage.receiveInvalidData,
);
}
}
}
10 changes: 10 additions & 0 deletions lib/utils/error/error_message.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'package:flutter/material.dart';

@immutable
class ErrorMessage {
const ErrorMessage._();

static const invalidParameter = 'パラメータが有効ではありません。';
static const unknown = '予期せぬエラーが発生しました。';
static const receiveInvalidData = '不適切なデータを取得しました。';
}
7 changes: 7 additions & 0 deletions lib/view/weather_view/component/weather_forecast_panel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import 'package:flutter_training/state/weather_state_notifier.dart';
class WeatherForecastPanel extends ConsumerWidget {
const WeatherForecastPanel({super.key});

@visibleForTesting
static final minTempKey = UniqueKey();
@visibleForTesting
static final maxTempKey = UniqueKey();

@override
Widget build(BuildContext context, WidgetRef ref) {
final textTheme = Theme.of(context).textTheme;
Expand All @@ -28,6 +33,7 @@ class WeatherForecastPanel extends ConsumerWidget {
style: textTheme.labelLarge!.copyWith(
color: Colors.blue,
),
key: minTempKey,
),
),
Expanded(
Expand All @@ -37,6 +43,7 @@ class WeatherForecastPanel extends ConsumerWidget {
style: textTheme.labelLarge!.copyWith(
color: Colors.red,
),
key: maxTempKey,
),
),
],
Expand Down
9 changes: 8 additions & 1 deletion lib/view/weather_view/weather_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import 'package:flutter_training/view/weather_view/component/weather_forecast_pa
class WeatherPage extends ConsumerWidget {
const WeatherPage({super.key});

@visibleForTesting
static final closeButtonKey = UniqueKey();
@visibleForTesting
static final reloadButton = UniqueKey();

@override
Widget build(BuildContext context, WidgetRef ref) {
void onReloaded(WeatherRequest request) {
Expand Down Expand Up @@ -36,8 +41,9 @@ class WeatherPage extends ConsumerWidget {
children: [
Expanded(
child: TextButton(
child: const Text('Close'),
onPressed: () => Navigator.pop(context),
key: closeButtonKey,
child: const Text('Close'),
),
),
Expanded(
Expand All @@ -50,6 +56,7 @@ class WeatherPage extends ConsumerWidget {
),
);
},
key: reloadButton,
child: const Text('Reload'),
),
),
Expand Down
12 changes: 3 additions & 9 deletions test/model/weather_forecast_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,11 @@ import 'package:flutter_training/model/weather_condition.dart';
import 'package:flutter_training/model/weather_forecast.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

import '../utils/dummy_data.dart';

void main() {
test('success case: fromJson', () {
const jsonData = '''
{
"weather_condition": "cloudy",
"max_temperature": 25,
"min_temperature": 7,
"date": "2023-09-19 10:24:31.877"
}
''';
final data = jsonDecode(jsonData) as Map<String, dynamic>;
final data = jsonDecode(validJsonData) as Map<String, dynamic>;
final result = WeatherForecast.fromJson(data);

expect(
Expand Down
19 changes: 6 additions & 13 deletions test/service/weather_service_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,14 @@ import 'package:flutter_training/model/weather_forecast.dart';
import 'package:flutter_training/model/weather_request.dart';
import 'package:flutter_training/service/weather_service.dart';
import 'package:flutter_training/utils/api/result.dart';
import 'package:flutter_training/utils/error/error_message.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:yumemi_weather/yumemi_weather.dart';

import '../utils/dummy_data.dart';
import 'weather_service_test.mocks.dart';

const jsonData = '''
{
"weather_condition": "cloudy",
"max_temperature": 25,
"min_temperature": 7,
"date": "2023-09-19T00:00:00.000"
}
''';

final request = WeatherRequest(
area: 'Nagoya',
date: DateTime(2023, 9, 19),
Expand All @@ -42,7 +35,7 @@ void main() {
});

test('success case', () {
when(mockClient.fetchWeather(any)).thenReturn(jsonData);
when(mockClient.fetchWeather(any)).thenReturn(validJsonData);

final result = container.read(weatherServiceProvider).fetchWeather(request);

Expand All @@ -55,7 +48,7 @@ void main() {
weatherCondition: WeatherCondition.cloudy,
maxTemperature: 25,
minTemperature: 7,
date: DateTime(2023, 9, 19),
date: DateTime(2023, 9, 19, 10, 24, 31, 877),
),
),
);
Expand All @@ -74,7 +67,7 @@ void main() {
isA<Failure<WeatherForecast, String>>().having(
(error) => error.exception,
'error message',
'パラメータが有効ではありません。',
ErrorMessage.invalidParameter,
),
);
});
Expand All @@ -90,7 +83,7 @@ void main() {
isA<Failure<WeatherForecast, String>>().having(
(error) => error.exception,
'error message',
'予期せぬエラーが発生しました。',
ErrorMessage.unknown,
),
);
});
Expand Down
36 changes: 9 additions & 27 deletions test/state/weather_state_notifier_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,14 @@ import 'package:flutter_training/model/weather_forecast.dart';
import 'package:flutter_training/model/weather_request.dart';
import 'package:flutter_training/service/weather_service.dart';
import 'package:flutter_training/state/weather_state_notifier.dart';
import 'package:flutter_training/utils/error/error_message.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:yumemi_weather/yumemi_weather.dart';

import '../utils/dummy_data.dart';
import 'weather_state_notifier_test.mocks.dart';

const jsonData = '''
{
"weather_condition": "cloudy",
"max_temperature": 25,
"min_temperature": 7,
"date": "2023-09-19T00:00:00.000"
}
''';

const invalidJsonDataForCheckedFromJsonException = '''
{
"weather_condition": "thunder",
"max_temperature": 25.0,
"min_temperature": 7,
"date": "2023-09-19T00:00:00.000"
}
''';

const invalidJsonDataForFormatException = '{invalid json data}';

final request = WeatherRequest(
area: 'Nagoya',
date: DateTime(2023, 9, 19),
Expand All @@ -53,7 +35,7 @@ void main() {
});

test('success case: getWeather()', () {
when(mockClient.fetchWeather(any)).thenReturn(jsonData);
when(mockClient.fetchWeather(any)).thenReturn(validJsonData);

// 天気の取得処理を実行して、結果をstateに流す
container
Expand All @@ -68,7 +50,7 @@ void main() {
weatherCondition: WeatherCondition.cloudy,
maxTemperature: 25,
minTemperature: 7,
date: DateTime(2023, 9, 19),
date: DateTime(2023, 9, 19, 10, 24, 31, 877),
),
);
});
Expand All @@ -92,7 +74,7 @@ void main() {

//取得はできてない
expect(weatherState, null);
expect(errorMessage, '予期せぬエラーが発生しました。');
expect(errorMessage, ErrorMessage.unknown);
});

test('invalidParameter error is thrown', () {
Expand All @@ -114,10 +96,10 @@ void main() {

//取得はできてない
expect(weatherState, null);
expect(errorMessage, 'パラメータが有効ではありません。');
expect(errorMessage, ErrorMessage.invalidParameter);
});

test('CheckedFromJsonException error is thrown', () {
test('fromJson error case: CheckedFromJsonException should be thrown.', () {
when(mockClient.fetchWeather(any))
.thenReturn(invalidJsonDataForCheckedFromJsonException);

Expand All @@ -136,7 +118,7 @@ void main() {

//取得はできてない
expect(weatherState, null);
expect(errorMessage, '不適切なデータを取得しました。');
expect(errorMessage, ErrorMessage.receiveInvalidData);
});

test('received data is not in JSON format', () {
Expand All @@ -158,7 +140,7 @@ void main() {

//取得はできてない
expect(weatherState, null);
expect(errorMessage, '不適切なデータを取得しました。');
expect(errorMessage, ErrorMessage.receiveInvalidData);
});
});
}
46 changes: 46 additions & 0 deletions test/utils/dummy_data.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const validJsonData = '''
{
"weather_condition": "cloudy",
"max_temperature": 25,
"min_temperature": 7,
"date": "2023-09-19 10:24:31.877"
}
''';

const sunnyWeatherJsonData = '''
{
"weather_condition": "sunny",
"max_temperature": 30,
"min_temperature": 0,
"date": "2023-09-19 10:24:31.877"
}
''';

const cloudyWeatherJsonData = '''
{
"weather_condition": "cloudy",
"max_temperature": 25,
"min_temperature": 7,
"date": "2023-09-19 10:24:31.877"
}
''';

const rainyWeatherJsonData = '''
{
"weather_condition": "rainy",
"max_temperature": 44,
"min_temperature": -22,
"date": "2023-09-19 10:24:31.877"
}
''';

const invalidJsonDataForCheckedFromJsonException = '''
{
"weather_condition": "thunder",
"max_temperature": 25.0,
"min_temperature": 7,
"date": "2023-09-19T00:00:00.000"
}
''';

const invalidJsonDataForFormatException = '{invalid json data}';
19 changes: 19 additions & 0 deletions test/utils/utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';

import 'package:flutter_test/flutter_test.dart';

final binding = TestWidgetsFlutterBinding.ensureInitialized();

//初期状態は横画面判定になっているので縦画面に設定する
void setDisplaySize({
Size size = const Size(390, 844),
}) {
binding.platformDispatcher.implicitView!.physicalSize = size;
binding.platformDispatcher.implicitView!.devicePixelRatio = 1;
}

//デバイスサイズ設定の破棄
void teardownDisplaySize() {
binding.platformDispatcher.implicitView!.resetPhysicalSize();
binding.platformDispatcher.implicitView!.resetDevicePixelRatio();
}
Loading

0 comments on commit 3ac3c70

Please sign in to comment.