diff --git a/.gitignore b/.gitignore index 74075fee83a423a3ea896786e4a215617db7b886..1d1cba0359900f3b8b9387ed877ce484ab6ac9e7 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 5152928dd41a382ec940e6c70f01b2a577ce1967..bbaab2e2298c4dbdffac098396832457062318a8 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 ce1ef8a18c5dea8bbac554858aeb33d4b69c529d..c0cd3b9ce99b4eb35c4c4dc18a209bfdb77bdaa9 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 ccdef33ac22d7d8582b9320bf670d969d3833f10..ef270c1308eac5a92002e1feb196716e16d79a2b 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 d3300d9926059c30f47b2856583eb9a918e4e62c..35815df7beab61b7f8a426bdd919b73347c45645 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 7c0e3fd76e05e99cc1f5f289afb64b45d8aa2aaf..0000000000000000000000000000000000000000 --- 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 dbdf552e195ad1ccf803eb43c122d1073c41ec24..18dd95a6ca838f20bfb24afc7991f7503c2cba19 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 3cfd5b0b6789adf0781ce1b2bebf2a6c47cab44f..f48e12096c42686d676a7430e5ed1924f6f887d5 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 0000000000000000000000000000000000000000..3b6dd90cd801d225230db6a5a918b739b35285c3 --- /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); + }); + }); +}