From 63b01bc9e371c554cd2b46ea9688b4a1a31677c2 Mon Sep 17 00:00:00 2001 From: Adrien Bonnin <adbonnin@ippon.fr> Date: Sun, 18 Sep 2022 20:04:10 +0200 Subject: [PATCH] :sparkles: Ajout des tests pour le overrides --- .gitignore | 1 + .gitlab-ci.yml | 2 + README.md | 16 +- lib/app.dart | 1 + .../06_state_notifier_provider.dart | 19 -- lib/toto.md | 177 ----------- pubspec.lock | 280 ++++++++++++++++++ pubspec.yaml | 2 + test/presentation/07_overrides_test.dart | 41 +++ 9 files changed, 342 insertions(+), 197 deletions(-) delete mode 100644 lib/toto.md create mode 100644 test/presentation/07_overrides_test.dart diff --git a/.gitignore b/.gitignore index 74075fe..1d1cba0 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ migrate_working_dir/ .pub-cache/ .pub/ /build/ +*.mocks.dart # Web related lib/generated_plugin_registrant.dart diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5152928..bbaab2e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,6 +7,8 @@ test:flutter: stage: test image: cirrusci/flutter script: + - flutter pub get + - flutter pub run build_runner build --delete-conflicting-outputs - flutter test build:web-main: diff --git a/README.md b/README.md index ce1ef8a..c0cd3b9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,18 @@ # Riverpod par la pratique * [Découverte des bases](docs/01_decouverte_des_bases.md) -* [Intégration avec Flutter](docs/02_integration_avec_flutter.md) \ No newline at end of file +* [Intégration avec Flutter](docs/02_integration_avec_flutter.md) + +## Environnement de développement + +Récupérer les dépendances : + +```shell script +$ flutter pub get +``` + +Lancer le générateur de code : + +```shell script +$ flutter pub run build_runner build --delete-conflicting-outputs +``` \ No newline at end of file diff --git a/lib/app.dart b/lib/app.dart index ccdef33..ef270c1 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -48,6 +48,7 @@ class App extends StatelessWidget { GoRoute( path: example.path, builder: (_, __) => ExampleScreen(example), + parentNavigatorKey: _rootNavigatorKey, ), ], ), diff --git a/lib/presentation/06_state_notifier_provider.dart b/lib/presentation/06_state_notifier_provider.dart index d3300d9..35815df 100644 --- a/lib/presentation/06_state_notifier_provider.dart +++ b/lib/presentation/06_state_notifier_provider.dart @@ -130,17 +130,6 @@ class TodosChangeNotifier extends ChangeNotifier { } } -class MutableTodosChangeNotifier extends ChangeNotifier { - MutableTodosChangeNotifier(this.todos); - - final List<Todo> todos; - - void updateTodo(Todo updatedTodo) { - todos.updateTodo(updatedTodo); - notifyListeners(); - } -} - const todos = [ Todo( id: 0, @@ -200,12 +189,4 @@ extension TodoListExtension on List<Todo> { List<Todo> copyWithTodo(Todo updatedTodo) => // [for (Todo todo in this) todo.id == updatedTodo.id ? updatedTodo : todo]; - - void updateTodo(Todo updatedTodo) { - for (int i = 0; i < length; i++) { - if (this[i].id == updatedTodo.id) { - this[i] = updatedTodo; - } - } - } } diff --git a/lib/toto.md b/lib/toto.md deleted file mode 100644 index 7c0e3fd..0000000 --- a/lib/toto.md +++ /dev/null @@ -1,177 +0,0 @@ -```dart -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -class TodoExample extends StatelessWidget { - const TodoExample({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return const ProviderScope( - child: TodoList(), - ); - } -} - -class TodoList extends ConsumerStatefulWidget { - const TodoList({Key? key}) : super(key: key); - - @override - ConsumerState<TodoList> createState() => _TodoListState(); -} - -class _TodoListState extends ConsumerState<TodoList> { - bool _isUncheckedFilter = false; - - @override - Widget build(BuildContext context) { - final todos = ref.watch(todosProvider.select((tds) => tds.whereTodos(_isUncheckedFilter))); - - return Column( - children: [ - TodoListFilter( - filter: _isUncheckedFilter, - onFilterChanged: _onFilterChanged, - ), - const Divider(height: 1), - Expanded( - child: ListView.builder( - itemBuilder: (_, index) => - TodoListItem( - todos[index], - onCheckedChanged: (value) => _onCheckedChanged(todos[index], value), - ), - itemCount: todos.length, - ), - ), - ], - ); - } - - void _onFilterChanged(bool value) { - setState(() => _isUncheckedFilter = value); - } - - void _onCheckedChanged(Todo todo, bool? value) { - final updatedTodo = todo.copyWith(checked: value != null && value); - - final todos = ref.read(todosProvider.notifier).state; - ref.read(todosProvider.notifier).state = todos.copyWithTodo(updatedTodo); - } -} - -class TodoListFilter extends StatelessWidget { - const TodoListFilter({ - Key? key, - required this.filter, - required this.onFilterChanged, - }) : super(key: key); - - final bool filter; - final ValueChanged<bool> onFilterChanged; - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text("Unchecked"), - Switch( - value: filter, - onChanged: onFilterChanged, - ), - ], - ), - ); - } -} - -class TodoListItem extends StatelessWidget { - const TodoListItem(this.todo, { - Key? key, - this.onCheckedChanged, - }) : super(key: key); - - final Todo todo; - final ValueChanged<bool?>? onCheckedChanged; - - @override - Widget build(BuildContext context) { - return CheckboxListTile( - value: todo.checked, - onChanged: onCheckedChanged, - title: Text( - todo.title, - overflow: TextOverflow.ellipsis, - ), - controlAffinity: ListTileControlAffinity.leading, - ); - } -} - -final todosProvider = StateProvider<List<Todo>>((ref) => todos); - -const todos = [ - Todo( - id: 0, - title: 'Unit test passed', - checked: true, - ), - Todo( - id: 1, - title: 'Code reviewed', - checked: true, - ), - Todo( - id: 2, - title: 'Acceptance criteria for each issue met', - ), - Todo( - id: 3, - title: 'Functional tests passed', - ), - Todo( - id: 4, - title: 'Non-functional requirements met', - ), - Todo( - id: 5, - title: 'Product owner accepts the User Story', - ), -]; - -class Todo { - const Todo({ - required this.id, - required this.title, - this.checked = false, - }); - - final int id; - final String title; - final bool checked; - - Todo copyWith({ - int? id, - String? title, - bool? checked, - }) { - return Todo( - id: id ?? this.id, - title: title ?? this.title, - checked: checked ?? this.checked, - ); - } -} - -extension TodoListExtension on List<Todo> { - List<Todo> whereTodos(bool isUnchecked) => // - where((todo) => !isUnchecked || !todo.checked).toList(); - - List<Todo> copyWithTodo(Todo updatedTodo) => // - [for (Todo todo in this) todo.id == updatedTodo.id ? updatedTodo : todo]; -} - -``` \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index dbdf552..18dd95a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,27 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "47.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "4.7.0" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" async: dependency: transitive description: @@ -15,6 +36,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.10" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "7.2.4" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "8.4.1" characters: dependency: transitive description: @@ -29,6 +106,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" clock: dependency: transitive description: @@ -36,6 +120,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "4.3.0" collection: dependency: transitive description: @@ -43,6 +134,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.16.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" cupertino_icons: dependency: "direct main" description: @@ -50,6 +155,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.5" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.4" fake_async: dependency: transitive description: @@ -57,6 +169,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.4" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" flutter: dependency: "direct main" description: flutter @@ -86,6 +212,20 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" go_router: dependency: "direct main" description: @@ -93,6 +233,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.5.0" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" js: dependency: transitive description: @@ -100,6 +268,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.4" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "4.6.0" lints: dependency: transitive description: @@ -135,6 +310,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.7.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + mockito: + dependency: "direct dev" + description: + name: mockito + url: "https://pub.dartlang.org" + source: hosted + version: "5.3.1" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" path: dependency: transitive description: @@ -142,6 +338,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.1" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" riverpod: dependency: transitive description: @@ -149,11 +366,32 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0-dev.9" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.3" source_span: dependency: transitive description: @@ -182,6 +420,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" string_scanner: dependency: transitive description: @@ -203,6 +448,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.9" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" vector_math: dependency: transitive description: @@ -210,6 +469,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.2" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" sdks: dart: ">=2.17.1 <3.0.0" flutter: ">=3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 3cfd5b0..f48e120 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,6 +47,8 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^2.0.0 + mockito: ^5.1.0 + build_runner: ^2.1.10 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/test/presentation/07_overrides_test.dart b/test/presentation/07_overrides_test.dart new file mode 100644 index 0000000..3b6dd90 --- /dev/null +++ b/test/presentation/07_overrides_test.dart @@ -0,0 +1,41 @@ +import 'package:article_flutter_riverpod/presentation/07_overrides_example.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import '07_overrides_test.mocks.dart'; + +@GenerateMocks([TodosRepository]) +void main() { + group('TodoExample', () { + testWidgets('doit vérifier', (tester) async { + // given: + const mocksTodos = [ + Todo(id: 0, title: 'Test'), + ]; + + // and: + final mockTodosRepository = MockTodosRepository(); + when(mockTodosRepository.getTodos()).thenReturn(mocksTodos); + + // when: + await tester.pumpWidget( + ProviderScope( + overrides: [ + todosRepositoryProvider.overrideWithValue(mockTodosRepository), + ], + child: const MaterialApp( + home: Material( + child: TodoList(), + ), + ), + ), + ); + + // then: + expect(find.byType(TodoListItem), findsOneWidget); + }); + }); +} -- GitLab