reis-mobile 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/.claude-plugin/marketplace.json +24 -0
  2. package/.claude-plugin/plugin.json +12 -0
  3. package/CHANGELOG.md +103 -0
  4. package/LICENSE +21 -0
  5. package/README.md +631 -0
  6. package/THIRD_PARTY_NOTICES.md +105 -0
  7. package/agents/flutter-architect.md +92 -0
  8. package/agents/flutter-performance-engineer.md +103 -0
  9. package/agents/flutter-test-engineer.md +100 -0
  10. package/agents/lead-mobile.md +107 -0
  11. package/agents/mobile-code-reviewer.md +66 -0
  12. package/agents/mobile-staff-engineer.md +85 -0
  13. package/agents/plugin-native-expert.md +97 -0
  14. package/bin/reis-mobile.mjs +256 -0
  15. package/commands/debate.md +135 -0
  16. package/commands/doctor.md +23 -0
  17. package/commands/reis-mobile.md +50 -0
  18. package/commands/review.md +53 -0
  19. package/core/config/language.mjs +83 -0
  20. package/core/context/context-engine.mjs +113 -0
  21. package/core/detection/project-probe.mjs +104 -0
  22. package/core/detection/stack-detector.mjs +204 -0
  23. package/core/diagnostics/doctor.mjs +181 -0
  24. package/core/install/claude-plugin.mjs +76 -0
  25. package/core/paths.mjs +4 -0
  26. package/core/registry/frontmatter.mjs +81 -0
  27. package/core/registry/registry.mjs +140 -0
  28. package/core/router/intent-detector.mjs +75 -0
  29. package/core/router/intents.mjs +91 -0
  30. package/core/router/router.mjs +83 -0
  31. package/core/security/redact.mjs +24 -0
  32. package/package.json +35 -0
  33. package/skills/architecture-feature-first/SKILL.md +116 -0
  34. package/skills/bloc/SKILL.md +354 -0
  35. package/skills/code-review/SKILL.md +89 -0
  36. package/skills/dart-3-updates/SKILL.md +223 -0
  37. package/skills/dart-add-unit-test/SKILL.md +126 -0
  38. package/skills/dart-collect-coverage/SKILL.md +145 -0
  39. package/skills/dart-fix-runtime-errors/SKILL.md +170 -0
  40. package/skills/dart-generate-test-mocks/SKILL.md +159 -0
  41. package/skills/dart-migrate-to-checks-package/SKILL.md +532 -0
  42. package/skills/dart-resolve-package-conflicts/SKILL.md +120 -0
  43. package/skills/dart-run-static-analysis/SKILL.md +108 -0
  44. package/skills/dart-use-pattern-matching/SKILL.md +240 -0
  45. package/skills/dart-use-pattern-matching/examples/json_patterns.dart +85 -0
  46. package/skills/detect-mobile-stack/SKILL.md +33 -0
  47. package/skills/effective-dart/SKILL.md +187 -0
  48. package/skills/firebase-ai/SKILL.md +93 -0
  49. package/skills/firebase-analytics/SKILL.md +129 -0
  50. package/skills/firebase-app-check/SKILL.md +124 -0
  51. package/skills/firebase-auth/SKILL.md +224 -0
  52. package/skills/firebase-cloud-firestore/SKILL.md +165 -0
  53. package/skills/firebase-cloud-functions/SKILL.md +148 -0
  54. package/skills/firebase-crashlytics/SKILL.md +182 -0
  55. package/skills/firebase-data-connect/SKILL.md +80 -0
  56. package/skills/firebase-database/SKILL.md +224 -0
  57. package/skills/firebase-in-app-messaging/SKILL.md +111 -0
  58. package/skills/firebase-messaging/SKILL.md +201 -0
  59. package/skills/firebase-remote-config/SKILL.md +131 -0
  60. package/skills/firebase-storage/SKILL.md +158 -0
  61. package/skills/flutter-add-integration-test/SKILL.md +167 -0
  62. package/skills/flutter-add-widget-preview/SKILL.md +149 -0
  63. package/skills/flutter-add-widget-test/SKILL.md +158 -0
  64. package/skills/flutter-adding-home-screen-widgets/SKILL.md +205 -0
  65. package/skills/flutter-animating-apps/SKILL.md +185 -0
  66. package/skills/flutter-app-architecture/SKILL.md +80 -0
  67. package/skills/flutter-apply-architecture-best-practices/SKILL.md +166 -0
  68. package/skills/flutter-build-responsive-layout/SKILL.md +143 -0
  69. package/skills/flutter-building-forms/SKILL.md +125 -0
  70. package/skills/flutter-building-layouts/SKILL.md +131 -0
  71. package/skills/flutter-building-plugins/SKILL.md +209 -0
  72. package/skills/flutter-caching-data/SKILL.md +171 -0
  73. package/skills/flutter-change-notifier/SKILL.md +138 -0
  74. package/skills/flutter-embedding-native-views/SKILL.md +200 -0
  75. package/skills/flutter-errors/SKILL.md +126 -0
  76. package/skills/flutter-fix-layout-issues/SKILL.md +134 -0
  77. package/skills/flutter-handling-concurrency/SKILL.md +182 -0
  78. package/skills/flutter-implement-json-serialization/SKILL.md +157 -0
  79. package/skills/flutter-improving-accessibility/SKILL.md +113 -0
  80. package/skills/flutter-interoperating-with-native-apis/SKILL.md +130 -0
  81. package/skills/flutter-login-usecase/SKILL.md +38 -0
  82. package/skills/flutter-managing-state/SKILL.md +206 -0
  83. package/skills/flutter-project-audit/SKILL.md +66 -0
  84. package/skills/flutter-reducing-app-size/SKILL.md +103 -0
  85. package/skills/flutter-secure-token-store/SKILL.md +35 -0
  86. package/skills/flutter-setting-up-on-macos/SKILL.md +78 -0
  87. package/skills/flutter-setup-declarative-routing/SKILL.md +259 -0
  88. package/skills/flutter-setup-localization/SKILL.md +214 -0
  89. package/skills/flutter-theming-apps/SKILL.md +163 -0
  90. package/skills/flutter-use-http-package/SKILL.md +178 -0
  91. package/skills/flutter-widget-review/SKILL.md +54 -0
  92. package/skills/flutter-working-with-databases/SKILL.md +203 -0
  93. package/skills/flutterfire-configure/SKILL.md +143 -0
  94. package/skills/mobile-security-audit/SKILL.md +101 -0
  95. package/skills/mockito/SKILL.md +149 -0
  96. package/skills/mocktail/SKILL.md +122 -0
  97. package/skills/patrol-e2e-testing/SKILL.md +224 -0
  98. package/skills/provider/SKILL.md +139 -0
  99. package/skills/riverpod/SKILL.md +246 -0
  100. package/skills/testing/SKILL.md +50 -0
  101. package/stacks/android/stack.json +8 -0
  102. package/stacks/flutter/stack.json +8 -0
  103. package/stacks/ios/stack.json +8 -0
  104. package/stacks/kotlin-multiplatform/stack.json +8 -0
  105. package/stacks/react-native/stack.json +8 -0
@@ -0,0 +1,159 @@
1
+ ---
2
+ name: dart-generate-test-mocks
3
+ description: Define and generate mock objects for external dependencies using `package:mockito` and `build_runner`. Use when unit testing classes that depend on complex external services like APIs or databases.
4
+ metadata:
5
+ model: models/gemini-3.1-pro-preview
6
+ last_modified: Fri, 24 Apr 2026 15:13:58 GMT
7
+ intents: [test]
8
+ stacks: [flutter]
9
+ source: https://github.com/dart-lang/skills
10
+ license: BSD-3-Clause
11
+ ---
12
+ # Testing and Mocking Dart Applications
13
+
14
+ ## Contents
15
+ - [Structuring Code for Testability](#structuring-code-for-testability)
16
+ - [Managing Dependencies](#managing-dependencies)
17
+ - [Generating Mocks](#generating-mocks)
18
+ - [Implementing Unit Tests](#implementing-unit-tests)
19
+ - [Workflow: Creating and Running Mocked Tests](#workflow-creating-and-running-mocked-tests)
20
+ - [Examples](#examples)
21
+
22
+ ## Structuring Code for Testability
23
+ Design Dart classes to support dependency injection. Isolate complex external dependencies (like API clients or databases) so they can be replaced with mock objects during testing.
24
+
25
+ - Inject external services (e.g., `http.Client`) through class constructors.
26
+ - Represent URLs strictly as `Uri` objects using `Uri.parse(string)`.
27
+ - Utilize Dart's object-oriented features (classes, mixins) to define clear interfaces for external interactions.
28
+
29
+ ## Managing Dependencies
30
+ Configure the `pubspec.yaml` file with the necessary testing and code generation packages.
31
+
32
+ - Add runtime dependencies (e.g., `package:http`) using `dart pub add http`.
33
+ - Add testing dependencies using `dart pub add dev:test dev:mockito dev:build_runner`.
34
+ - Import HTTP libraries with a prefix to avoid namespace collisions: `import 'package:http/http.dart' as http;`.
35
+
36
+ ## Generating Mocks
37
+ Use `package:mockito` and `build_runner` to automatically generate mock classes for fixed scenarios and behavior verification.
38
+
39
+ - Always use the `@GenerateNiceMocks` annotation (preferable to `@GenerateMocks` to avoid missing stub exceptions).
40
+ - Place the annotation in the test file, passing a list of `MockSpec<Type>()` objects.
41
+ - Import the generated file using the `.mocks.dart` extension.
42
+ - Execute `build_runner` to generate the mock files: `dart run build_runner build`.
43
+
44
+ ## Implementing Unit Tests
45
+ Isolate the system under test using the generated mock objects. Use `package:test` to structure the test suite.
46
+
47
+ - **Stubbing:** Configure mock behavior before interacting with the system under test.
48
+ - Use `when(mock.method()).thenReturn(value)` for synchronous methods.
49
+ - **CRITICAL:** Always use `thenAnswer((_) async => value)` for methods returning a `Future` or `Stream`. Never use `thenReturn` for asynchronous returns.
50
+ - **Verification:** Assert that the system under test interacted with the mock object correctly.
51
+ - Use `verify(mock.method()).called(1)` to check exact invocation counts.
52
+ - Use argument matchers like `any`, `anyNamed`, or `captureAny` for flexible verification.
53
+
54
+ ## Workflow: Creating and Running Mocked Tests
55
+
56
+ Use the following checklist to implement and verify mocked unit tests.
57
+
58
+ ### Task Progress
59
+ - [ ] 1. Identify the external dependency to mock (e.g., `http.Client`).
60
+ - [ ] 2. Inject the dependency into the target class constructor.
61
+ - [ ] 3. Create a test file (e.g., `target_test.dart`) and add `@GenerateNiceMocks([MockSpec<Dependency>()])`.
62
+ - [ ] 4. Add the `part` or `import` directive for the generated `.mocks.dart` file.
63
+ - [ ] 5. Run `dart run build_runner build` to generate the mock classes.
64
+ - [ ] 6. Write the test cases using `group()` and `test()`.
65
+ - [ ] 7. Stub required behaviors using `when()`.
66
+ - [ ] 8. Execute the target method.
67
+ - [ ] 9. Verify interactions using `verify()` and assert outcomes using `expect()`.
68
+ - [ ] 10. Run the test suite using `dart test`.
69
+
70
+ ### Feedback Loop: Test Failures
71
+ If tests fail or `build_runner` encounters errors:
72
+ 1. **Run validator:** Execute `dart test` or `dart run build_runner build`.
73
+ 2. **Review errors:** Check for missing stubs, mismatched argument matchers, or syntax errors in the generated files.
74
+ 3. **Fix:**
75
+ - If a mock method throws an unexpected null error, ensure you used `@GenerateNiceMocks`.
76
+ - If an async stub throws an `ArgumentError`, change `thenReturn` to `thenAnswer`.
77
+ - If `build_runner` fails, ensure the `.mocks.dart` import matches the file name exactly.
78
+ 4. Repeat until all tests pass.
79
+
80
+ ## Examples
81
+
82
+ ### High-Fidelity Mocking and Testing Example
83
+
84
+ **1. System Under Test (`lib/api_service.dart`)**
85
+ ```dart
86
+ import 'dart:convert';
87
+ import 'package:http/http.dart' as http;
88
+
89
+ class ApiService {
90
+ final http.Client client;
91
+
92
+ ApiService(this.client);
93
+
94
+ Future<String> fetchData(String urlString) async {
95
+ final uri = Uri.parse(urlString);
96
+ final response = await client.get(uri);
97
+
98
+ if (response.statusCode == 200) {
99
+ return jsonDecode(response.body)['data'];
100
+ } else {
101
+ throw Exception('Failed to load data');
102
+ }
103
+ }
104
+ }
105
+ ```
106
+
107
+ **2. Test Implementation (`test/api_service_test.dart`)**
108
+ ```dart
109
+ import 'package:test/test.dart';
110
+ import 'package:mockito/annotations.dart';
111
+ import 'package:mockito/mockito.dart';
112
+ import 'package:http/http.dart' as http;
113
+ import 'package:my_app/api_service.dart';
114
+
115
+ // Generate the mock class for http.Client
116
+ @GenerateNiceMocks([MockSpec<http.Client>()])
117
+ import 'api_service_test.mocks.dart';
118
+
119
+ void main() {
120
+ group('ApiService', () {
121
+ late ApiService apiService;
122
+ late MockClient mockHttpClient;
123
+
124
+ setUp(() {
125
+ mockHttpClient = MockClient();
126
+ apiService = ApiService(mockHttpClient);
127
+ });
128
+
129
+ test('returns data if the http call completes successfully', () async {
130
+ // Arrange: Stub the async HTTP GET request using thenAnswer
131
+ when(mockHttpClient.get(any)).thenAnswer(
132
+ (_) async => http.Response('{"data": "Success"}', 200),
133
+ );
134
+
135
+ // Act
136
+ final result = await apiService.fetchData('https://api.example.com/data');
137
+
138
+ // Assert
139
+ expect(result, 'Success');
140
+
141
+ // Verify the mock was called with the correct Uri
142
+ verify(mockHttpClient.get(Uri.parse('https://api.example.com/data'))).called(1);
143
+ });
144
+
145
+ test('throws an exception if the http call completes with an error', () {
146
+ // Arrange
147
+ when(mockHttpClient.get(any)).thenAnswer(
148
+ (_) async => http.Response('Not Found', 404),
149
+ );
150
+
151
+ // Act & Assert
152
+ expect(
153
+ apiService.fetchData('https://api.example.com/data'),
154
+ throwsException,
155
+ );
156
+ });
157
+ });
158
+ }
159
+ ```
@@ -0,0 +1,532 @@
1
+ ---
2
+ name: dart-migrate-to-checks-package
3
+ description: |-
4
+ Replace the usage of `expect` and similar functions from `package:matcher`
5
+ to `package:checks` equivalents.
6
+ metadata:
7
+ model: models/gemini-3.1-pro-preview
8
+ last_modified: Tue, 09 Jun 2026 19:30:00 GMT
9
+ intents: [migration]
10
+ stacks: [flutter]
11
+ source: https://github.com/dart-lang/skills
12
+ license: BSD-3-Clause
13
+ ---
14
+ # Migrating Dart Tests to Package Checks
15
+
16
+ Use this skill when you need to migrate a Dart test suite from the legacy
17
+ `package:matcher` (which is exported by default from `package:test/test.dart`)
18
+ to the modern, type-safe, and literate `package:checks` assertion library.
19
+
20
+ ## Contents
21
+ - [When to Use This Skill](#when-to-use-this-skill)
22
+ - [How to Use This Skill (The Workflow)](#how-to-use-this-skill-the-workflow)
23
+ - [Key Syntax Differences and Pitfalls](#key-syntax-differences-and-pitfalls)
24
+ - [Matcher-to-Checks Mapping Table](#matcher-to-checks-mapping-table)
25
+ - [Matchers with No Direct Replacements](#matchers-with-no-direct-replacements)
26
+ - [Strategies for Discovery](#strategies-for-discovery)
27
+ - [Examples](#examples)
28
+
29
+ ---
30
+
31
+ ## When to Use This Skill
32
+ - When asked to "migrate tests to checks", "use package:checks", or
33
+ "modernize test assertions".
34
+ - When updating legacy test suites where static type safety, better
35
+ autocomplete in IDEs, and highly detailed failure diagnostics are desired.
36
+
37
+ ---
38
+
39
+ ## How to Use This Skill (The Workflow)
40
+
41
+ Follow this structured workflow to safely and systematically migrate a test suite:
42
+
43
+ ### 1. Dependency Setup
44
+ - Add `package:checks` as a `dev_dependency` in `pubspec.yaml`:
45
+ ```bash
46
+ dart pub add dev:checks
47
+ ```
48
+ - Remove `package:matcher` if it is explicitly listed under `dev_dependencies`
49
+ (it is typically transitively included by `package:test`, which is fine).
50
+
51
+ ### 2. Identify and Plan Target Files
52
+ - Use the grep patterns in [Strategies for Discovery](#strategies-for-discovery)
53
+ to locate all test files containing legacy `expect` or `expectLater` calls.
54
+ - Decide whether to migrate files fully or incrementally.
55
+
56
+ ### 3. Migrating a File (Incremental or Full)
57
+ For any target test file:
58
+ 1. **Update Imports**:
59
+ - Replace the generic `import 'package:test/test.dart';` with:
60
+ ```dart
61
+ import 'package:test/scaffolding.dart';
62
+ import 'package:checks/checks.dart';
63
+ ```
64
+ - **For Incremental Migration**: If you only want to migrate some test cases
65
+ in the file, or want to migrate one step at a time, add:
66
+ ```dart
67
+ import 'package:test/expect.dart'; // Temporarily allows legacy expect()
68
+ ```
69
+ 2. **Translate Assertions**: Rewrite legacy `expect` and `expectLater` calls
70
+ to `check` syntax following the [Key Syntax Differences and
71
+ Pitfalls](#key-syntax-differences-and-pitfalls) and the
72
+ [Matcher-to-Checks Mapping Table](#matcher-to-checks-mapping-table).
73
+ 3. **Verify via Compiler**: If migrating fully, remove the `import
74
+ 'package:test/expect.dart';` line. Any remaining un-migrated `expect`
75
+ calls will immediately surface as compiler errors, making them easy to
76
+ find and fix.
77
+
78
+ ### 4. Verification and Feedback Loops
79
+ - **Static Analysis**: Run static analysis on the target package:
80
+ ```bash
81
+ dart analyze
82
+ ```
83
+ Pay close attention to generic type parameters on `.isA<Type>()` and
84
+ ensure asynchronous expectations are properly awaited (check for
85
+ `unawaited_futures` warnings).
86
+ - **Run Tests**: Execute the tests to verify both behavior and correct
87
+ assertion runtime logic:
88
+ ```bash
89
+ dart test
90
+ ```
91
+ If a test fails, review the extremely detailed failure output of
92
+ `package:checks` to diagnose if the test is genuinely failing or if the
93
+ expectation was translated incorrectly.
94
+
95
+ ---
96
+
97
+ ## Key Syntax Differences and Pitfalls
98
+
99
+ > [!IMPORTANT]
100
+ > A line-for-line translation can sometimes introduce subtle bugs or false
101
+ > passes. Always review these key differences carefully:
102
+
103
+ ### 1. Collection Equality Pitfall (`equals` vs `deepEquals`)
104
+ - **Legacy Matcher**: `expect(actual, expected)` or `expect(actual,
105
+ equals(expected))` performed a **deep equality check** if the arguments
106
+ were collections (Lists, Maps, Sets).
107
+ - **Package Checks**: `.equals(expected)` corresponds strictly to
108
+ `operator ==`. Since Dart collections do not override `operator ==` for
109
+ element-wise comparison, using `.equals` on a collection will check for
110
+ *identity* and almost certainly fail at runtime.
111
+ - **Remediation**: You **must** replace collection equality assertions with
112
+ `.deepEquals(expected)`.
113
+ ```dart
114
+ // BEFORE (Matcher)
115
+ expect(myList, [1, 2, 3]);
116
+
117
+ // AFTER (Checks)
118
+ check(myList).deepEquals([1, 2, 3]);
119
+ ```
120
+
121
+ ### 2. The `reason` Parameter is now `because`
122
+ - **Legacy Matcher**: The explanation was passed as a trailing named
123
+ argument `reason` to `expect`:
124
+ ```dart
125
+ expect(actual, expectation, reason: 'Explanation');
126
+ ```
127
+ - **Package Checks**: The explanation is passed as the named argument
128
+ `because` to the `check` function *before* the actual subject:
129
+ ```dart
130
+ check(because: 'Explanation', actual).expectation();
131
+ ```
132
+
133
+ ### 3. Regular Expression Matching (`matches` vs `matchesPattern`)
134
+ - **Legacy Matcher**: The `matches(pattern)` matcher automatically converted
135
+ a `String` argument into a `RegExp` (e.g., `matches(r'\d')` matched `'1'`).
136
+ - **Package Checks**: `.matchesPattern(pattern)` treats a `String` argument
137
+ as a literal string pattern.
138
+ - **Remediation**: To match using a regular expression, you must explicitly
139
+ pass a `RegExp` object:
140
+ ```dart
141
+ // BEFORE (Matcher)
142
+ expect(someString, matches(r'\d+'));
143
+
144
+ // AFTER (Checks)
145
+ check(someString).matchesPattern(RegExp(r'\d+'));
146
+ ```
147
+
148
+ ### 4. Property Extraction (`TypeMatcher.having` vs `.has`)
149
+ - **Legacy Matcher**: Chained field/property expectations used
150
+ `TypeMatcher.having(feature, description, matcher)`:
151
+ ```dart
152
+ expect(actual, isA<Person>().having((p) => p.name, 'name', startsWith('A')));
153
+ ```
154
+ - **Package Checks**: The `.has(feature, description)` extension is
155
+ available on all `Subject`s, takes one fewer argument, and returns a new
156
+ `Subject` representing that property. You chain expectations directly off
157
+ it:
158
+ ```dart
159
+ check(actual).isA<Person>().has((p) => p.name, 'name').startsWith('A');
160
+ ```
161
+
162
+ ### 5. Synchronous vs. Asynchronous `throws`
163
+ - **Legacy Matcher**: In `package:matcher`, `throwsA` behaved similarly for both
164
+ synchronous closures and asynchronous futures when wrapped in `expect` or
165
+ `expectLater`.
166
+ - **Package Checks**: The `.throws<E>()` expectation behaves differently and
167
+ has different return types depending on whether the subject is synchronous or
168
+ asynchronous:
169
+ - **Synchronous** (`Subject<T Function()>`): `.throws<E>()` returns a
170
+ `Subject<E>` synchronously. This **does not** accept a callback argument!
171
+ You chain or cascade expectations directly off the returned `Subject<E>`:
172
+ ```dart
173
+ // YES (Synchronous chaining)
174
+ check(() => triggerSyncError()).throws<ArgumentError>()
175
+ ..has((e) => e.message, 'message').equals('invalid input');
176
+
177
+ // NO (Passing a callback to sync throws will cause a compiler error!)
178
+ check(() => triggerSync").throws<ArgumentError>((it) => ...); // ERROR!
179
+ ```
180
+ - **Asynchronous** (`Subject<Future<T>>`): `.throws<E>()` returns
181
+ `Future<void>`. Because you cannot chain directly off a `Future<void>`, this
182
+ **requires** an inspection callback:
183
+ ```dart
184
+ // YES (Asynchronous callback)
185
+ await check(triggerAsyncError()).throws<ArgumentError>((it) => it
186
+ ..has((e) => e.message, 'message').equals('invalid input'));
187
+ ```
188
+ - **Crucial Pitfall**: Trying to chain expectations directly after an awaited
189
+ asynchronous `.throws<E>()` (e.g.,
190
+ `await check(future).throws<E>().equals(...)`) will fail to compile
191
+ because it returns `Future<void>`.
192
+
193
+ ### 6. RegExp / Pattern Equality
194
+ - **Legacy Matcher**: In `package:matcher`, `expect(myPattern,`
195
+ `equals(RegExp('Hello')))` worked because the matcher comparison rules
196
+ handled RegExp instances.
197
+ - **Package Checks**: `.equals()` uses strict Dart `==` equality. Since separate
198
+ `RegExp` instances do not satisfy `==`, using `.equals()` will fail at runtime.
199
+ - **Remediation**: Use `.isA<RegExp>()` type refinement along with cascades to
200
+ assert on the properties of the `RegExp` object explicitly:
201
+ ```dart
202
+ check(myPattern).isA<RegExp>()
203
+ ..has((r) => r.pattern, 'pattern').equals('Hello')
204
+ ..has((r) => r.isMultiLine, 'isMultiLine').isTrue();
205
+ ```
206
+
207
+ ### 7. Strict Nullable Boolean Safety (`bool?` fields)
208
+ - **Legacy Matcher**: Statically, `isTrue` and `isFalse` performed loose
209
+ dynamic checks at runtime, which silently accepted nullable booleans (`bool?`).
210
+ - **Package Checks**: `.isTrue()` and `.isFalse()` are defined strictly on
211
+ `Subject<bool>` (non-nullable). They are **not** available on `Subject<bool?>`.
212
+ - **Remediation**: For fields declared as `bool?`, you must either refine the
213
+ subject (e.g., `.isNotNull().isTrue()`) or simply use `.equals(true)` and
214
+ `.equals(false)` which are generic and work on all types:
215
+ ```dart
216
+ // If options.flagOutdated is a bool?
217
+ check(options.flagOutdated).equals(true);
218
+ check(options.flagOutdated).equals(false);
219
+ ```
220
+
221
+ ### 8. Map Key Containment (`containsKey` vs `contains`)
222
+ - **Legacy Matcher**: In `package:matcher`, `contains(key)` was used to assert
223
+ that a `Map` contained a specific key.
224
+ - **Package Checks**: Calling `.contains(...)` on a `Subject<Map>` is not
225
+ defined and will fail compilation.
226
+ - **Remediation**: Use the map-specific `.containsKey(key)` matcher instead:
227
+ ```dart
228
+ // BEFORE (Matcher)
229
+ expect(myMap, contains('my_key'));
230
+
231
+ // AFTER (Checks)
232
+ check(myMap).containsKey('my_key');
233
+ ```
234
+
235
+ ### 9. Explicit Generic Parameters for Extension Types
236
+ - **Legacy Matcher**: `expect(extensionTypeConst, 3)` compiled because of loose
237
+ dynamic equality.
238
+ - **Package Checks**: If `QrEciValue` is an extension type representation of `int`
239
+ (e.g., `extension type const QrEciValue(int value) implements int`), calling
240
+ `.equals(3)` on a `Subject<QrEciValue>` fails because `3` (an `int`) is not
241
+ assignable to `QrEciValue`. Casting with `as int` will trigger an
242
+ "Unnecessary cast" static analysis warning because `QrEciValue` statically
243
+ implements `int`.
244
+ - **Remediation**: Explicitly specify the generic type parameter on the `check`
245
+ function to force checks to treat it as the primitive type:
246
+ ```dart
247
+ // YES (Type-safe and warning-free)
248
+ check<int>(QrEciValue.iso8859_1).equals(3);
249
+ ```
250
+
251
+ ### 10. Dynamic Map / JSON Lookup Casting
252
+ - **Legacy Matcher**: Loose dynamic typing allowed comparing nested json lookups
253
+ statically typed as `dynamic` directly against lists or maps.
254
+ - **Package Checks**: Strict type safety rejects the implicit assignment of
255
+ `dynamic` to `Iterable<Object?>` in `.deepEquals(...)`.
256
+ - **Remediation**: Statically cast the dynamic lookup result to a `List` or `Map`:
257
+ ```dart
258
+ // YES (Explicit cast to List)
259
+ check(myIterable).deepEquals(json['data']['items'] as List);
260
+ ```
261
+
262
+ ---
263
+
264
+ ## Matcher-to-Checks Mapping Table
265
+
266
+ Use this table as a quick reference for direct matcher replacements:
267
+
268
+ | Legacy Matcher | Package Checks Equivalent | Notes |
269
+ | :--- | :--- | :--- |
270
+ | `expect(actual, expected)` | `check(actual).equals(expected)` | Use `.deepEquals` for collections! |
271
+ | `expect(actual, equals(expected))` | `check(actual).equals(expected)` | Use `.deepEquals` for collections! |
272
+ | `isA<T>()` | `check(actual).isA<T>()` | Chaining is supported directly |
273
+ | `same(expected)` | `check(actual).identicalTo(expected)` | Verifies identity |
274
+ | `anyElement(matcher)` | `check(iterable).any(conditionCallback)` | E.g. `check(list).any((e) => e.equals(1))` |
275
+ | `everyElement(matcher)` | `check(iterable).every(conditionCallback)` | E.g. `check(list).every((e) => e.isGreaterThan(0))` |
276
+ | `hasLength(expected)` | `check(actual).length.equals(expected)` | Works on String, Map, Iterable, etc. |
277
+ | `isNot(matcher)` | `check(actual).not(conditionCallback)` | E.g. `check(val).not((it) => it.equals(5))` |
278
+ | `contains(element)` | `check(actual).contains(element)` | Works on String, Iterable (use `containsKey` for Map!) |
279
+ | `contains(key)` (on a Map) | `check(map).containsKey(key)` | Map key containment |
280
+ | `startsWith(prefix)` | `check(string).startsWith(prefix)` | String only |
281
+ | `endsWith(suffix)` | `check(string).endsWith(suffix)` | String only |
282
+ | `isEmpty` | `check(actual).isEmpty()` | Works on String, Map, Iterable |
283
+ | `isNotEmpty` | `check(actual).isNotEmpty()` | Works on String, Map, Iterable |
284
+ | `isNull` | `check(actual).isNull()` | |
285
+ | `isNotNull` | `check(actual).isNotNull()` | |
286
+ | `isTrue` / `true` | `check(actual).isTrue()` | Works on non-nullable `bool` only |
287
+ | `isFalse` / `false` | `check(actual).isFalse()` | Works on non-nullable `bool` only |
288
+ | `completion(matcher)` | `await check(future).completes(conditionCallback)` | Must be awaited! |
289
+ | `throwsA(matcher)` | `await check(future).throws<Type>()` | Must be awaited! |
290
+ | `emits(value)` | `await check(streamQueue).emits(conditionCallback)` | Must be awaited! |
291
+ | `emitsThrough(value)` | `await check(streamQueue).emitsThrough(conditionCallback)` | Must be awaited! |
292
+ | `stringContainsInOrder(list)` | `check(string).containsInOrder(list)` | String only |
293
+ | `pairwiseCompare(...)` | `check(actual).pairwiseMatches(...)` | |
294
+
295
+ ---
296
+
297
+ ## Matchers with No Direct Replacements
298
+
299
+ Some legacy matchers do not have a one-to-one equivalent in `package:checks`
300
+ due to API cleanup. Use these standard workarounds:
301
+
302
+ ### 1. Specific Error Matchers
303
+ - **Legacy**: `throwsArgumentError`, `throwsStateError`,
304
+ `throwsUnsupportedError`, etc.
305
+ - **Checks**: Use `.throws<T>()` with the specific error type:
306
+ ```dart
307
+ await check(triggerError()).throws<ArgumentError>();
308
+ ```
309
+
310
+ ### 2. The `anything` Matcher
311
+ - **Legacy**: `expect(actual, anything)`
312
+ - **Checks**: Pass an empty condition callback `(_) {}` when a condition is
313
+ syntactically required:
314
+ ```dart
315
+ await check(someFuture).completes((_) {});
316
+ ```
317
+
318
+ ### 3. Specific Numeric Toggles
319
+ - **Legacy**: `isPositive`, `isNegative`, `isZero`, `isNonPositive`,
320
+ `isNonNegative`, `isNonZero`
321
+ - **Checks**: Use explicit comparative expectations:
322
+ - `isPositive` $\rightarrow$ `isGreaterThan(0)`
323
+ - `isNegative` $\rightarrow$ `isLessThan(0)`
324
+ - `isZero` $\rightarrow$ `equals(0)`
325
+ - `isNonNegative` $\rightarrow$ `isGreaterOrEqual(0)`
326
+
327
+ ### 4. Numeric Ranges
328
+ - **Legacy**: `inClosedOpenRange(min, max)`, `inInclusiveRange(min, max)`,
329
+ etc.
330
+ - **Checks**: Chain the boundaries using the cascade operator (`..`):
331
+ ```dart
332
+ check(actualValue)
333
+ ..isGreaterOrEqual(min)
334
+ ..isLessThan(max);
335
+ ```
336
+
337
+ ---
338
+
339
+ ## Writing Custom Expectations (Replacing Custom Matchers)
340
+
341
+ When migrating from a legacy codebase, you may encounter custom `Matcher`
342
+ subclasses. In `package:checks`, custom assertions are implemented as
343
+ `extension` methods on `Subject<T>`.
344
+
345
+ To write custom expectations, you must import the checks context API:
346
+ ```dart
347
+ import 'package:checks/context.dart';
348
+ ```
349
+
350
+ ### 1. Simple Custom Expectations (using `expect`)
351
+ Use `context.expect` to check a property and return a `Rejection` on failure:
352
+ ```dart
353
+ extension CustomPersonChecks on Subject<Person> {
354
+ void isAdult() {
355
+ context.expect(
356
+ () => ['is an adult (age >= 18)'],
357
+ (actual) {
358
+ if (actual.age >= 18) return null; // Pass
359
+ return Rejection(
360
+ which: ['is only ${actual.age} years old'],
361
+ );
362
+ },
363
+ );
364
+ }
365
+ }
366
+ ```
367
+
368
+ ### 2. Nested Property Extraction (using `nest` or `has`)
369
+ To extract a property and allow further chained checks, use `nest` or the
370
+ simpler `has` helper:
371
+ - **Using `has` (Recommended for simple, non-failing field access)**:
372
+ ```dart
373
+ extension CustomPersonChecks on Subject<Person> {
374
+ Subject<Address> get address => has((p) => p.address, 'address');
375
+ }
376
+ ```
377
+ - **Using `nest` (For property extraction that can fail or reject)**:
378
+ ```dart
379
+ extension CustomPersonChecks on Subject<Person> {
380
+ Subject<String> get ssn => context.nest(
381
+ 'has a valid SSN',
382
+ (actual) {
383
+ final ssnValue = actual.ssn;
384
+ if (ssnValue == null) {
385
+ return Extracted.rejection(which: ['has no SSN']);
386
+ }
387
+ return Extracted.value(ssnValue);
388
+ },
389
+ );
390
+ }
391
+ ```
392
+
393
+ ### 3. Asynchronous Custom Expectations
394
+ If the expectation is asynchronous (e.g. checking a Future or Stream), use
395
+ `context.expectAsync` or `context.nestAsync` and return the resulting `Future`:
396
+ ```dart
397
+ extension CustomFutureChecks<T> on Subject<Future<T>> {
398
+ Future<void> completesNormally() {
399
+ return context.expectAsync(
400
+ () => ['completes without throwing'],
401
+ (actual) async {
402
+ try {
403
+ await actual;
404
+ return null; // Pass
405
+ } catch (e) {
406
+ return Rejection(which: ['threw $e']);
407
+ }
408
+ },
409
+ );
410
+ }
411
+ }
412
+ ```
413
+
414
+ ---
415
+
416
+ ## Strategies for Discovery
417
+
418
+ Execute these commands in the terminal to identify legacy matchers and files
419
+ requiring migration:
420
+
421
+ ```bash
422
+ # 1. Find all test files containing legacy expect() or expectLater()
423
+ grep -rn "expect(" test/
424
+ grep -rn "expectLater(" test/
425
+
426
+ # 2. Find potential collection equality pitfalls (literal lists or maps)
427
+ grep -rn "expect(.*, \[" test/
428
+ grep -rn "expect(.*, {" test/
429
+
430
+ # 3. Find matches() calls (need conversion to RegExp + matchesPattern)
431
+ grep -rn "matches(" test/
432
+
433
+ # 4. Find legacy TypeMatcher.having() calls (which need conversion to .has())
434
+ grep -rn "having(" test/
435
+ ```
436
+
437
+ ---
438
+
439
+ ## Examples
440
+
441
+ ### Basic Assertions
442
+ **Before (Matcher):**
443
+ ```dart
444
+ expect(someValue, isNotNull);
445
+ expect(result, isTrue, reason: 'should be successful');
446
+ expect(myString, startsWith('hello'));
447
+ ```
448
+
449
+ **After (Checks):**
450
+ ```dart
451
+ check(someValue).isNotNull();
452
+ check(because: 'should be successful', result).isTrue();
453
+ check(myString).startsWith('hello');
454
+ ```
455
+
456
+ ### Collection and Deep Equality
457
+ **Before (Matcher):**
458
+ ```dart
459
+ expect(items, [1, 2, 3]);
460
+ expect(configMap, equals({'port': 8080}));
461
+ ```
462
+
463
+ **After (Checks):**
464
+ ```dart
465
+ check(items).deepEquals([1, 2, 3]);
466
+ check(configMap).deepEquals({'port': 8080});
467
+ ```
468
+
469
+ ### Chaining and Cascades
470
+ **Before (Matcher):**
471
+ ```dart
472
+ expect(someString, allOf([
473
+ startsWith('a'),
474
+ contains('b'),
475
+ endsWith('c'),
476
+ ]));
477
+ ```
478
+
479
+ **After (Checks):**
480
+ ```dart
481
+ check(someString)
482
+ ..startsWith('a')
483
+ ..contains('b')
484
+ ..endsWith('c');
485
+ ```
486
+
487
+ ### Complex Property Matching (has)
488
+ **Before (Matcher):**
489
+ ```dart
490
+ expect(response, isA<Response>()
491
+ .having((r) => r.statusCode, 'statusCode', 200)
492
+ .having((r) => r.body, 'body', contains('success')));
493
+ ```
494
+
495
+ **After (Checks):**
496
+ ```dart
497
+ check(response).isA<Response>()
498
+ ..has((r) => r.statusCode, 'statusCode').equals(200)
499
+ ..has((r) => r.body, 'body').contains('success');
500
+ ```
501
+
502
+ ### Asynchronous Futures
503
+ **Before (Matcher):**
504
+ ```dart
505
+ expect(fetchData(), completes);
506
+ expect(fetchData(), completion(equals('data')));
507
+ expect(failingCall(), throwsA(isA<StateError>()));
508
+ ```
509
+
510
+ **After (Checks):**
511
+ ```dart
512
+ await check(fetchData()).completes();
513
+ await check(fetchData()).completes((it) => it.equals('data'));
514
+ await check(failingCall()).throws<StateError>();
515
+ ```
516
+
517
+ ### Asynchronous Streams
518
+ **Before (Matcher):**
519
+ ```dart
520
+ var queue = StreamQueue(Stream.fromIterable([1, 2, 3]));
521
+ await expectLater(queue, emitsInOrder([1, 2, 3]));
522
+ ```
523
+
524
+ **After (Checks):**
525
+ ```dart
526
+ var queue = StreamQueue(Stream.fromIterable([1, 2, 3]));
527
+ await check(queue).inOrder([
528
+ (s) => s.emits((e) => e.equals(1)),
529
+ (s) => s.emits((e) => e.equals(2)),
530
+ (s) => s.emits((e) => e.equals(3)),
531
+ ]);
532
+ ```