@aicgen/aicgen 1.0.0-beta.1 → 1.0.0

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 (160) hide show
  1. package/{.claude/guidelines → .agent/rules}/api-design.md +5 -1
  2. package/{.claude/guidelines → .agent/rules}/architecture.md +5 -1
  3. package/{.claude/guidelines → .agent/rules}/best-practices.md +5 -1
  4. package/{.claude/guidelines → .agent/rules}/code-style.md +5 -1
  5. package/{.claude/guidelines → .agent/rules}/design-patterns.md +5 -1
  6. package/{.claude/guidelines → .agent/rules}/devops.md +5 -1
  7. package/{.claude/guidelines → .agent/rules}/error-handling.md +5 -1
  8. package/.agent/rules/instructions.md +28 -0
  9. package/{.claude/guidelines → .agent/rules}/language.md +5 -1
  10. package/{.claude/guidelines → .agent/rules}/performance.md +5 -1
  11. package/{.claude/guidelines → .agent/rules}/security.md +5 -1
  12. package/{.claude/guidelines → .agent/rules}/testing.md +5 -1
  13. package/.agent/workflows/add-documentation.md +10 -0
  14. package/.agent/workflows/generate-integration-tests.md +10 -0
  15. package/.agent/workflows/generate-unit-tests.md +11 -0
  16. package/.agent/workflows/performance-audit.md +11 -0
  17. package/.agent/workflows/refactor-extract-module.md +12 -0
  18. package/.agent/workflows/security-audit.md +12 -0
  19. package/.gemini/instructions.md +4843 -0
  20. package/.vs/ProjectSettings.json +2 -2
  21. package/.vs/VSWorkspaceState.json +15 -15
  22. package/.vs/aicgen.slnx/v18/DocumentLayout.json +53 -53
  23. package/AGENTS.md +9 -11
  24. package/assets/icon.svg +33 -33
  25. package/bun.lock +734 -26
  26. package/{CLAUDE.md → claude.md} +2 -2
  27. package/config.example.yml +129 -0
  28. package/config.yml +38 -0
  29. package/data/architecture/microservices/api-gateway.md +56 -56
  30. package/data/devops/observability.md +73 -73
  31. package/data/guideline-mappings.yml +128 -0
  32. package/data/language/dart/async.md +289 -0
  33. package/data/language/dart/basics.md +280 -0
  34. package/data/language/dart/error-handling.md +355 -0
  35. package/data/language/dart/index.md +10 -0
  36. package/data/language/dart/testing.md +352 -0
  37. package/data/language/swift/basics.md +477 -0
  38. package/data/language/swift/concurrency.md +654 -0
  39. package/data/language/swift/error-handling.md +679 -0
  40. package/data/language/swift/swiftui-mvvm.md +795 -0
  41. package/data/language/swift/testing.md +708 -0
  42. package/data/version.json +10 -8
  43. package/dist/index.js +50153 -28959
  44. package/jest.config.js +46 -0
  45. package/package.json +14 -3
  46. package/.claude/agents/architecture-reviewer.md +0 -88
  47. package/.claude/agents/guideline-checker.md +0 -73
  48. package/.claude/agents/security-auditor.md +0 -108
  49. package/.claude/settings.json +0 -98
  50. package/.claude/settings.local.json +0 -8
  51. package/.eslintrc.json +0 -28
  52. package/.github/workflows/release.yml +0 -180
  53. package/.github/workflows/test.yml +0 -81
  54. package/CONTRIBUTING.md +0 -821
  55. package/dist/commands/init.d.ts +0 -8
  56. package/dist/commands/init.d.ts.map +0 -1
  57. package/dist/commands/init.js +0 -46
  58. package/dist/commands/init.js.map +0 -1
  59. package/dist/config/profiles.d.ts +0 -4
  60. package/dist/config/profiles.d.ts.map +0 -1
  61. package/dist/config/profiles.js +0 -30
  62. package/dist/config/profiles.js.map +0 -1
  63. package/dist/config/settings.d.ts +0 -7
  64. package/dist/config/settings.d.ts.map +0 -1
  65. package/dist/config/settings.js +0 -7
  66. package/dist/config/settings.js.map +0 -1
  67. package/dist/index.d.ts +0 -3
  68. package/dist/index.d.ts.map +0 -1
  69. package/dist/index.js.map +0 -1
  70. package/dist/models/guideline.d.ts +0 -15
  71. package/dist/models/guideline.d.ts.map +0 -1
  72. package/dist/models/guideline.js +0 -2
  73. package/dist/models/guideline.js.map +0 -1
  74. package/dist/models/preference.d.ts +0 -9
  75. package/dist/models/preference.d.ts.map +0 -1
  76. package/dist/models/preference.js +0 -2
  77. package/dist/models/preference.js.map +0 -1
  78. package/dist/models/profile.d.ts +0 -9
  79. package/dist/models/profile.d.ts.map +0 -1
  80. package/dist/models/profile.js +0 -2
  81. package/dist/models/profile.js.map +0 -1
  82. package/dist/models/project.d.ts +0 -13
  83. package/dist/models/project.d.ts.map +0 -1
  84. package/dist/models/project.js +0 -2
  85. package/dist/models/project.js.map +0 -1
  86. package/dist/services/ai/anthropic.d.ts +0 -7
  87. package/dist/services/ai/anthropic.d.ts.map +0 -1
  88. package/dist/services/ai/anthropic.js +0 -39
  89. package/dist/services/ai/anthropic.js.map +0 -1
  90. package/dist/services/generator.d.ts +0 -2
  91. package/dist/services/generator.d.ts.map +0 -1
  92. package/dist/services/generator.js +0 -4
  93. package/dist/services/generator.js.map +0 -1
  94. package/dist/services/learner.d.ts +0 -2
  95. package/dist/services/learner.d.ts.map +0 -1
  96. package/dist/services/learner.js +0 -4
  97. package/dist/services/learner.js.map +0 -1
  98. package/dist/services/scanner.d.ts +0 -3
  99. package/dist/services/scanner.d.ts.map +0 -1
  100. package/dist/services/scanner.js +0 -54
  101. package/dist/services/scanner.js.map +0 -1
  102. package/dist/utils/errors.d.ts +0 -15
  103. package/dist/utils/errors.d.ts.map +0 -1
  104. package/dist/utils/errors.js +0 -27
  105. package/dist/utils/errors.js.map +0 -1
  106. package/dist/utils/file.d.ts +0 -7
  107. package/dist/utils/file.d.ts.map +0 -1
  108. package/dist/utils/file.js +0 -32
  109. package/dist/utils/file.js.map +0 -1
  110. package/dist/utils/logger.d.ts +0 -6
  111. package/dist/utils/logger.d.ts.map +0 -1
  112. package/dist/utils/logger.js +0 -17
  113. package/dist/utils/logger.js.map +0 -1
  114. package/dist/utils/path.d.ts +0 -6
  115. package/dist/utils/path.d.ts.map +0 -1
  116. package/dist/utils/path.js +0 -14
  117. package/dist/utils/path.js.map +0 -1
  118. package/docs/planning/memory-lane.md +0 -83
  119. package/packaging/linux/aicgen.spec +0 -23
  120. package/packaging/linux/control +0 -9
  121. package/packaging/macos/scripts/postinstall +0 -12
  122. package/packaging/windows/setup.nsi +0 -92
  123. package/scripts/add-categories.ts +0 -87
  124. package/scripts/build-binary.ts +0 -46
  125. package/scripts/embed-data.ts +0 -105
  126. package/scripts/generate-version.ts +0 -150
  127. package/scripts/test-decompress.ts +0 -27
  128. package/scripts/test-extract.ts +0 -31
  129. package/src/__tests__/services/assistant-file-writer.test.ts +0 -400
  130. package/src/__tests__/services/guideline-loader.test.ts +0 -281
  131. package/src/__tests__/services/tarball-extraction.test.ts +0 -125
  132. package/src/commands/add-guideline.ts +0 -296
  133. package/src/commands/clear.ts +0 -61
  134. package/src/commands/guideline-selector.ts +0 -123
  135. package/src/commands/init.ts +0 -645
  136. package/src/commands/quick-add.ts +0 -586
  137. package/src/commands/remove-guideline.ts +0 -152
  138. package/src/commands/stats.ts +0 -49
  139. package/src/commands/update.ts +0 -240
  140. package/src/config.ts +0 -82
  141. package/src/embedded-data.ts +0 -1492
  142. package/src/index.ts +0 -67
  143. package/src/models/profile.ts +0 -24
  144. package/src/models/project.ts +0 -43
  145. package/src/services/assistant-file-writer.ts +0 -612
  146. package/src/services/config-generator.ts +0 -150
  147. package/src/services/config-manager.ts +0 -70
  148. package/src/services/data-source.ts +0 -248
  149. package/src/services/first-run-init.ts +0 -148
  150. package/src/services/guideline-loader.ts +0 -311
  151. package/src/services/hook-generator.ts +0 -178
  152. package/src/services/subagent-generator.ts +0 -310
  153. package/src/utils/banner.ts +0 -66
  154. package/src/utils/errors.ts +0 -27
  155. package/src/utils/file.ts +0 -67
  156. package/src/utils/formatting.ts +0 -172
  157. package/src/utils/logger.ts +0 -89
  158. package/src/utils/path.ts +0 -17
  159. package/src/utils/wizard-state.ts +0 -132
  160. package/tsconfig.json +0 -25
@@ -0,0 +1,355 @@
1
+ # Error Handling in Dart
2
+
3
+ ## Custom Exceptions
4
+
5
+ ```dart
6
+ // ✅ Create structured exception hierarchy
7
+ class AppException implements Exception {
8
+ final String message;
9
+ final int? code;
10
+ final dynamic details;
11
+
12
+ AppException(this.message, {this.code, this.details});
13
+
14
+ @override
15
+ String toString() => 'AppException: $message (code: $code)';
16
+ }
17
+
18
+ class NotFoundException extends AppException {
19
+ NotFoundException(String resource, String id)
20
+ : super('$resource with id $id not found', code: 404);
21
+ }
22
+
23
+ class ValidationException extends AppException {
24
+ ValidationException(String message, {Map<String, String>? errors})
25
+ : super(message, code: 400, details: errors);
26
+ }
27
+
28
+ // Usage
29
+ throw NotFoundException('User', userId);
30
+ throw ValidationException('Invalid input', errors: {'email': 'Invalid format'});
31
+ ```
32
+
33
+ ## Try-Catch-Finally
34
+
35
+ ```dart
36
+ // ✅ Catch specific exceptions first
37
+ Future<User> fetchUser(String id) async {
38
+ try {
39
+ final response = await http.get('/api/users/$id');
40
+ return User.fromJson(response.data);
41
+ } on NetworkException catch (e) {
42
+ print('Network error: ${e.message}');
43
+ throw AppException('Network request failed', details: e);
44
+ } on FormatException catch (e) {
45
+ print('Invalid JSON: $e');
46
+ throw AppException('Invalid response format');
47
+ } catch (e, stackTrace) {
48
+ print('Unexpected error: $e');
49
+ print('Stack trace: $stackTrace');
50
+ rethrow;
51
+ } finally {
52
+ print('Request completed');
53
+ }
54
+ }
55
+
56
+ // ✅ Use finally for cleanup
57
+ Future<void> processFile(String path) async {
58
+ File? file;
59
+ try {
60
+ file = File(path);
61
+ final content = await file.readAsString();
62
+ await processContent(content);
63
+ } catch (e) {
64
+ print('Error processing file: $e');
65
+ rethrow;
66
+ } finally {
67
+ // Always runs, even if exception thrown
68
+ await file?.close();
69
+ }
70
+ }
71
+ ```
72
+
73
+ ## Result Type Pattern
74
+
75
+ ```dart
76
+ // ✅ Explicit success/failure without exceptions
77
+ class Result<T> {
78
+ final T? data;
79
+ final AppException? error;
80
+
81
+ const Result.success(T data) : data = data, error = null;
82
+ const Result.failure(AppException error) : data = null, error = error;
83
+
84
+ bool get isSuccess => error == null;
85
+ bool get isFailure => error != null;
86
+ }
87
+
88
+ // Usage
89
+ Future<Result<User>> fetchUserSafe(String id) async {
90
+ try {
91
+ final user = await fetchUser(id);
92
+ return Result.success(user);
93
+ } on AppException catch (e) {
94
+ return Result.failure(e);
95
+ } catch (e) {
96
+ return Result.failure(AppException('Unknown error: $e'));
97
+ }
98
+ }
99
+
100
+ // Consumer handles explicitly
101
+ final result = await fetchUserSafe('123');
102
+ if (result.isSuccess) {
103
+ print('User: ${result.data!.name}');
104
+ } else {
105
+ print('Error: ${result.error!.message}');
106
+ }
107
+ ```
108
+
109
+ ## Either Type Pattern
110
+
111
+ ```dart
112
+ // ✅ Functional error handling
113
+ sealed class Either<L, R> {
114
+ const Either();
115
+ }
116
+
117
+ class Left<L, R> extends Either<L, R> {
118
+ final L value;
119
+ const Left(this.value);
120
+ }
121
+
122
+ class Right<L, R> extends Either<L, R> {
123
+ final R value;
124
+ const Right(this.value);
125
+ }
126
+
127
+ // Usage
128
+ Future<Either<AppException, User>> fetchUser(String id) async {
129
+ try {
130
+ final user = await _fetchUser(id);
131
+ return Right(user);
132
+ } on AppException catch (e) {
133
+ return Left(e);
134
+ }
135
+ }
136
+
137
+ // Pattern matching (Dart 3.0+)
138
+ final result = await fetchUser('123');
139
+ switch (result) {
140
+ case Left(value: final error):
141
+ print('Error: ${error.message}');
142
+ case Right(value: final user):
143
+ print('User: ${user.name}');
144
+ }
145
+ ```
146
+
147
+ ## Validation
148
+
149
+ ```dart
150
+ // ✅ Validate early, throw specific errors
151
+ class UserValidator {
152
+ static void validate(String email, String password) {
153
+ if (email.isEmpty) {
154
+ throw ValidationException('Email is required');
155
+ }
156
+
157
+ if (!email.contains('@')) {
158
+ throw ValidationException('Invalid email format');
159
+ }
160
+
161
+ if (password.length < 8) {
162
+ throw ValidationException('Password must be at least 8 characters');
163
+ }
164
+ }
165
+ }
166
+
167
+ // Usage
168
+ try {
169
+ UserValidator.validate(email, password);
170
+ await createUser(email, password);
171
+ } on ValidationException catch (e) {
172
+ showError(e.message);
173
+ }
174
+ ```
175
+
176
+ ## Assert for Development
177
+
178
+ ```dart
179
+ // ✅ Use assert for development-time checks
180
+ void transfer(Account from, Account to, double amount) {
181
+ assert(amount > 0, 'Amount must be positive');
182
+ assert(from.balance >= amount, 'Insufficient funds');
183
+
184
+ from.withdraw(amount);
185
+ to.deposit(amount);
186
+ }
187
+
188
+ // Assertions only run in debug mode
189
+ // In production, they're removed
190
+ ```
191
+
192
+ ## Error Boundaries (Flutter)
193
+
194
+ ```dart
195
+ // ✅ Catch errors at widget level
196
+ class ErrorBoundary extends StatefulWidget {
197
+ final Widget child;
198
+
199
+ const ErrorBoundary({required this.child});
200
+
201
+ @override
202
+ State<ErrorBoundary> createState() => _ErrorBoundaryState();
203
+ }
204
+
205
+ class _ErrorBoundaryState extends State<ErrorBoundary> {
206
+ Object? error;
207
+
208
+ @override
209
+ Widget build(BuildContext context) {
210
+ if (error != null) {
211
+ return ErrorWidget(error: error!);
212
+ }
213
+
214
+ return ErrorWrapper(
215
+ onError: (error, stackTrace) {
216
+ setState(() => this.error = error);
217
+ },
218
+ child: widget.child,
219
+ );
220
+ }
221
+ }
222
+ ```
223
+
224
+ ## Global Error Handling
225
+
226
+ ```dart
227
+ // ✅ Catch uncaught errors
228
+ void main() {
229
+ // Synchronous errors
230
+ FlutterError.onError = (details) {
231
+ print('Flutter error: ${details.exception}');
232
+ print('Stack trace: ${details.stack}');
233
+ reportError(details.exception, details.stack);
234
+ };
235
+
236
+ // Asynchronous errors
237
+ PlatformDispatcher.instance.onError = (error, stack) {
238
+ print('Async error: $error');
239
+ reportError(error, stack);
240
+ return true;
241
+ };
242
+
243
+ runApp(MyApp());
244
+ }
245
+
246
+ void reportError(Object error, StackTrace? stackTrace) {
247
+ // Send to error tracking service
248
+ }
249
+ ```
250
+
251
+ ## Retry Pattern
252
+
253
+ ```dart
254
+ // ✅ Retry with exponential backoff
255
+ Future<T> retryWithBackoff<T>(
256
+ Future<T> Function() operation, {
257
+ int maxAttempts = 3,
258
+ Duration initialDelay = const Duration(seconds: 1),
259
+ }) async {
260
+ int attempt = 0;
261
+ while (true) {
262
+ try {
263
+ return await operation();
264
+ } catch (e) {
265
+ attempt++;
266
+ if (attempt >= maxAttempts) rethrow;
267
+
268
+ final delay = initialDelay * (1 << attempt); // Exponential backoff
269
+ print('Retry attempt $attempt after $delay');
270
+ await Future.delayed(delay);
271
+ }
272
+ }
273
+ }
274
+
275
+ // Usage
276
+ final user = await retryWithBackoff(
277
+ () => fetchUser('123'),
278
+ maxAttempts: 5,
279
+ );
280
+ ```
281
+
282
+ ## Circuit Breaker Pattern
283
+
284
+ ```dart
285
+ // ✅ Prevent cascading failures
286
+ class CircuitBreaker {
287
+ final int failureThreshold;
288
+ final Duration timeout;
289
+
290
+ int _failureCount = 0;
291
+ DateTime? _openedAt;
292
+
293
+ CircuitBreaker({
294
+ this.failureThreshold = 5,
295
+ this.timeout = const Duration(minutes: 1),
296
+ });
297
+
298
+ Future<T> execute<T>(Future<T> Function() operation) async {
299
+ if (_isOpen()) {
300
+ if (_shouldReset()) {
301
+ _reset();
302
+ } else {
303
+ throw AppException('Circuit breaker is open');
304
+ }
305
+ }
306
+
307
+ try {
308
+ final result = await operation();
309
+ _onSuccess();
310
+ return result;
311
+ } catch (e) {
312
+ _onFailure();
313
+ rethrow;
314
+ }
315
+ }
316
+
317
+ bool _isOpen() => _openedAt != null;
318
+
319
+ bool _shouldReset() {
320
+ return _openedAt != null &&
321
+ DateTime.now().difference(_openedAt!) > timeout;
322
+ }
323
+
324
+ void _onSuccess() {
325
+ _failureCount = 0;
326
+ }
327
+
328
+ void _onFailure() {
329
+ _failureCount++;
330
+ if (_failureCount >= failureThreshold) {
331
+ _openedAt = DateTime.now();
332
+ }
333
+ }
334
+
335
+ void _reset() {
336
+ _failureCount = 0;
337
+ _openedAt = null;
338
+ }
339
+ }
340
+ ```
341
+
342
+ ## Best Practices
343
+
344
+ - Create custom exception classes for different error types
345
+ - Catch specific exceptions before general ones
346
+ - Use `rethrow` to preserve stack traces
347
+ - Always clean up resources in `finally` blocks
348
+ - Validate input early and throw meaningful errors
349
+ - Use Result/Either types for expected failures
350
+ - Implement global error handlers for uncaught errors
351
+ - Use retry with exponential backoff for transient failures
352
+ - Implement circuit breakers for external service calls
353
+ - Don't catch exceptions you can't handle
354
+ - Include context in error messages
355
+ - Log errors with stack traces
@@ -0,0 +1,10 @@
1
+ # Dart Language Guidelines
2
+
3
+ Comprehensive guidelines for writing idiomatic Dart code.
4
+
5
+ ## Topics
6
+
7
+ - [Fundamentals](basics.md) - Types, null safety, syntax
8
+ - [Async Programming](async.md) - Futures, async/await, Streams
9
+ - [Error Handling](error-handling.md) - Exceptions and error patterns
10
+ - [Testing](testing.md) - Unit and widget testing
@@ -0,0 +1,352 @@
1
+ # Testing in Dart
2
+
3
+ ## Unit Testing
4
+
5
+ ```dart
6
+ import 'package:test/test.dart';
7
+
8
+ // ✅ Arrange-Act-Assert pattern
9
+ void main() {
10
+ group('UserService', () {
11
+ test('should create user with hashed password', () async {
12
+ // Arrange
13
+ final repository = MockUserRepository();
14
+ final service = UserService(repository);
15
+ final userData = UserData(email: 'test@example.com', password: 'secret');
16
+
17
+ // Act
18
+ final user = await service.createUser(userData);
19
+
20
+ // Assert
21
+ expect(user.email, equals('test@example.com'));
22
+ expect(user.passwordHash, isNot(equals('secret')));
23
+ verify(repository.save(any)).called(1);
24
+ });
25
+
26
+ test('should throw ValidationException for invalid email', () {
27
+ final service = UserService(MockUserRepository());
28
+
29
+ expect(
30
+ () => service.createUser(UserData(email: 'invalid', password: 'pass')),
31
+ throwsA(isA<ValidationException>()),
32
+ );
33
+ });
34
+ });
35
+ }
36
+ ```
37
+
38
+ ## Test Organization
39
+
40
+ ```dart
41
+ void main() {
42
+ // ✅ Use group for related tests
43
+ group('Calculator', () {
44
+ late Calculator calculator;
45
+
46
+ // setUp runs before each test
47
+ setUp(() {
48
+ calculator = Calculator();
49
+ });
50
+
51
+ // tearDown runs after each test
52
+ tearDown(() {
53
+ calculator.dispose();
54
+ });
55
+
56
+ test('adds two numbers', () {
57
+ expect(calculator.add(2, 3), equals(5));
58
+ });
59
+
60
+ test('subtracts two numbers', () {
61
+ expect(calculator.subtract(5, 3), equals(2));
62
+ });
63
+
64
+ group('division', () {
65
+ test('divides two numbers', () {
66
+ expect(calculator.divide(10, 2), equals(5));
67
+ });
68
+
69
+ test('throws on division by zero', () {
70
+ expect(
71
+ () => calculator.divide(10, 0),
72
+ throwsA(isA<DivisionByZeroException>()),
73
+ );
74
+ });
75
+ });
76
+ });
77
+ }
78
+ ```
79
+
80
+ ## Matchers
81
+
82
+ ```dart
83
+ // Equality
84
+ expect(value, equals(expected));
85
+ expect(value, isNot(equals(unexpected)));
86
+
87
+ // Types
88
+ expect(value, isA<String>());
89
+ expect(value, isNotNull);
90
+ expect(value, isNull);
91
+
92
+ // Numbers
93
+ expect(value, greaterThan(5));
94
+ expect(value, lessThan(10));
95
+ expect(value, closeTo(3.14, 0.01));
96
+
97
+ // Strings
98
+ expect(text, contains('hello'));
99
+ expect(text, startsWith('Hello'));
100
+ expect(text, endsWith('world'));
101
+ expect(text, matches(r'^\d+$'));
102
+
103
+ // Collections
104
+ expect(list, isEmpty);
105
+ expect(list, isNotEmpty);
106
+ expect(list, hasLength(3));
107
+ expect(list, contains(item));
108
+ expect(map, containsValue(value));
109
+
110
+ // Futures
111
+ expect(future, completes);
112
+ expect(future, throwsException);
113
+ expect(future, completion(equals(value)));
114
+
115
+ // Custom matchers
116
+ expect(user, isA<User>().having((u) => u.email, 'email', 'test@example.com'));
117
+ ```
118
+
119
+ ## Mocking with Mockito
120
+
121
+ ```dart
122
+ import 'package:mockito/mockito.dart';
123
+ import 'package:mockito/annotations.dart';
124
+
125
+ // Generate mocks
126
+ @GenerateMocks([UserRepository, ApiClient])
127
+ void main() {
128
+ group('UserService', () {
129
+ late MockUserRepository repository;
130
+ late UserService service;
131
+
132
+ setUp(() {
133
+ repository = MockUserRepository();
134
+ service = UserService(repository);
135
+ });
136
+
137
+ test('should fetch user by id', () async {
138
+ // Arrange - stub method
139
+ final expectedUser = User(id: '1', name: 'Alice');
140
+ when(repository.findById('1')).thenAnswer((_) async => expectedUser);
141
+
142
+ // Act
143
+ final user = await service.getUser('1');
144
+
145
+ // Assert
146
+ expect(user, equals(expectedUser));
147
+ verify(repository.findById('1')).called(1);
148
+ });
149
+
150
+ test('should handle not found', () async {
151
+ // Stub to return null
152
+ when(repository.findById(any)).thenAnswer((_) async => null);
153
+
154
+ expect(
155
+ () => service.getUser('999'),
156
+ throwsA(isA<NotFoundException>()),
157
+ );
158
+ });
159
+ });
160
+ }
161
+ ```
162
+
163
+ ## Testing Async Code
164
+
165
+ ```dart
166
+ test('should fetch data asynchronously', () async {
167
+ final data = await fetchData();
168
+ expect(data, isNotNull);
169
+ });
170
+
171
+ test('should complete within timeout', () async {
172
+ await expectLater(
173
+ slowOperation().timeout(const Duration(seconds: 2)),
174
+ completes,
175
+ );
176
+ });
177
+
178
+ test('should handle errors', () {
179
+ expect(
180
+ failingOperation(),
181
+ throwsA(isA<NetworkException>()),
182
+ );
183
+ });
184
+ ```
185
+
186
+ ## Testing Streams
187
+
188
+ ```dart
189
+ test('should emit values from stream', () async {
190
+ final stream = Stream.fromIterable([1, 2, 3]);
191
+
192
+ expect(stream, emitsInOrder([1, 2, 3]));
193
+ });
194
+
195
+ test('should emit and complete', () {
196
+ expect(
197
+ countStream(3),
198
+ emitsInOrder([
199
+ 1,
200
+ 2,
201
+ 3,
202
+ emitsDone,
203
+ ]),
204
+ );
205
+ });
206
+
207
+ test('should handle stream errors', () {
208
+ expect(
209
+ errorStream(),
210
+ emitsInOrder([
211
+ 1,
212
+ emitsError(isA<Exception>()),
213
+ ]),
214
+ );
215
+ });
216
+ ```
217
+
218
+ ## Widget Testing (Flutter)
219
+
220
+ ```dart
221
+ import 'package:flutter_test/flutter_test.dart';
222
+
223
+ void main() {
224
+ testWidgets('should display user name', (WidgetTester tester) async {
225
+ // Arrange
226
+ final user = User(id: '1', name: 'Alice');
227
+
228
+ // Act
229
+ await tester.pumpWidget(
230
+ MaterialApp(home: UserProfile(user: user)),
231
+ );
232
+
233
+ // Assert
234
+ expect(find.text('Alice'), findsOneWidget);
235
+ });
236
+
237
+ testWidgets('should handle button tap', (WidgetTester tester) async {
238
+ bool tapped = false;
239
+
240
+ await tester.pumpWidget(
241
+ MaterialApp(
242
+ home: Scaffold(
243
+ body: ElevatedButton(
244
+ onPressed: () => tapped = true,
245
+ child: const Text('Tap me'),
246
+ ),
247
+ ),
248
+ ),
249
+ );
250
+
251
+ // Find and tap button
252
+ await tester.tap(find.text('Tap me'));
253
+ await tester.pump();
254
+
255
+ expect(tapped, isTrue);
256
+ });
257
+ }
258
+ ```
259
+
260
+ ## Integration Testing
261
+
262
+ ```dart
263
+ import 'package:integration_test/integration_test.dart';
264
+ import 'package:flutter_test/flutter_test.dart';
265
+
266
+ void main() {
267
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
268
+
269
+ group('Login flow', () {
270
+ testWidgets('should login successfully', (tester) async {
271
+ await tester.pumpWidget(MyApp());
272
+
273
+ // Enter credentials
274
+ await tester.enterText(
275
+ find.byKey(const Key('email_field')),
276
+ 'test@example.com',
277
+ );
278
+ await tester.enterText(
279
+ find.byKey(const Key('password_field')),
280
+ 'password123',
281
+ );
282
+
283
+ // Tap login
284
+ await tester.tap(find.byKey(const Key('login_button')));
285
+ await tester.pumpAndSettle();
286
+
287
+ // Verify navigation to home
288
+ expect(find.byType(HomePage), findsOneWidget);
289
+ });
290
+ });
291
+ }
292
+ ```
293
+
294
+ ## Test Doubles
295
+
296
+ ```dart
297
+ // Fake - working implementation (not for production)
298
+ class FakeUserRepository implements UserRepository {
299
+ final _users = <String, User>{};
300
+
301
+ @override
302
+ Future<User?> findById(String id) async {
303
+ return _users[id];
304
+ }
305
+
306
+ @override
307
+ Future<void> save(User user) async {
308
+ _users[user.id] = user;
309
+ }
310
+ }
311
+
312
+ // Stub - returns canned responses
313
+ class StubApiClient implements ApiClient {
314
+ @override
315
+ Future<Response> get(String url) async {
316
+ return Response(statusCode: 200, data: {'id': '1', 'name': 'Alice'});
317
+ }
318
+ }
319
+
320
+ // Mock - pre-programmed with expectations (use Mockito)
321
+ final mock = MockUserRepository();
322
+ when(mock.findById('1')).thenAnswer((_) async => testUser);
323
+ ```
324
+
325
+ ## Coverage
326
+
327
+ ```bash
328
+ # Run tests with coverage
329
+ dart test --coverage=coverage
330
+
331
+ # Generate HTML report
332
+ genhtml coverage/lcov.info -o coverage/html
333
+
334
+ # View coverage
335
+ open coverage/html/index.html
336
+ ```
337
+
338
+ ## Best Practices
339
+
340
+ - Use `group()` to organize related tests
341
+ - One test per behavior
342
+ - Use descriptive test names: "should [expected behavior] when [scenario]"
343
+ - Follow Arrange-Act-Assert pattern
344
+ - Test observable behavior, not implementation
345
+ - Use setUp/tearDown for common initialization
346
+ - Mock external dependencies
347
+ - Test edge cases and error conditions
348
+ - Keep tests fast and independent
349
+ - Use `pump()` and `pumpAndSettle()` in widget tests
350
+ - Verify interactions with mocks
351
+ - Aim for high code coverage (80%+)
352
+ - Run tests in CI/CD pipeline