@the-bearded-bear/claude-craft 8.8.1 → 8.8.2
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.
- package/Dev/i18n/de/Angular/commands/check-compliance.md +218 -168
- package/Dev/i18n/de/Common/agents/refactoring-specialist.md +146 -69
- package/Dev/i18n/de/Common/commands/add-technology.md +166 -68
- package/Dev/i18n/de/Common/commands/setup-ci.md +216 -33
- package/Dev/i18n/de/Laravel/commands/check-compliance.md +223 -115
- package/Dev/i18n/de/Python/checklists/new-feature.md +319 -44
- package/Dev/i18n/de/React/rules/02-architecture.md +483 -322
- package/Dev/i18n/de/React/rules/03-coding-standards.md +584 -368
- package/Dev/i18n/de/Symfony/commands/check-compliance.md +186 -114
- package/Dev/i18n/de/Symfony/rules/07-testing-symfony.md +679 -10
- package/Dev/i18n/de/UIUX/commands/a11y-component.md +284 -26
- package/Dev/i18n/de/UIUX/commands/design-tokens.md +199 -35
- package/Dev/i18n/de/UIUX/commands/user-flow.md +179 -16
- package/Dev/i18n/de/VueJS/commands/check-compliance.md +227 -102
- package/Dev/i18n/es/Angular/commands/check-compliance.md +217 -167
- package/Dev/i18n/es/Common/commands/add-technology.md +158 -60
- package/Dev/i18n/es/Laravel/commands/check-compliance.md +223 -115
- package/Dev/i18n/es/React/commands/check-testing.md +263 -169
- package/Dev/i18n/es/ReactNative/commands/check-testing.md +256 -198
- package/Dev/i18n/es/Symfony/commands/check-compliance.md +189 -117
- package/Dev/i18n/es/Symfony/commands/check-testing.md +301 -102
- package/Dev/i18n/es/Symfony/commands/migration-plan.md +267 -34
- package/Dev/i18n/es/UIUX/commands/a11y-component.md +274 -16
- package/Dev/i18n/es/UIUX/commands/design-tokens.md +186 -22
- package/Dev/i18n/es/UIUX/commands/user-flow.md +173 -10
- package/Dev/i18n/es/VueJS/commands/check-compliance.md +227 -102
- package/Dev/i18n/fr/Angular/commands/check-compliance.md +217 -167
- package/Dev/i18n/fr/Common/commands/ralph-run.md +138 -60
- package/Dev/i18n/fr/Laravel/commands/check-compliance.md +223 -115
- package/Dev/i18n/fr/React/commands/check-architecture.md +370 -53
- package/Dev/i18n/fr/React/commands/check-code-quality.md +454 -64
- package/Dev/i18n/fr/React/commands/check-testing.md +476 -102
- package/Dev/i18n/fr/Symfony/commands/check-compliance.md +188 -116
- package/Dev/i18n/fr/VueJS/commands/check-compliance.md +227 -102
- package/Dev/i18n/pt/Angular/commands/check-compliance.md +225 -175
- package/Dev/i18n/pt/Common/agents/ralph-conductor.md +211 -30
- package/Dev/i18n/pt/Common/commands/add-technology.md +149 -51
- package/Dev/i18n/pt/Common/commands/ralph-run.md +239 -35
- package/Dev/i18n/pt/Flutter/commands/analyze-performance.md +331 -46
- package/Dev/i18n/pt/Flutter/commands/check-compliance.md +234 -29
- package/Dev/i18n/pt/Flutter/commands/generate-feature.md +526 -51
- package/Dev/i18n/pt/Flutter/commands/generate-widget.md +515 -27
- package/Dev/i18n/pt/Flutter/commands/golden-update.md +322 -46
- package/Dev/i18n/pt/Flutter/commands/localization-check.md +278 -37
- package/Dev/i18n/pt/Laravel/commands/check-compliance.md +212 -104
- package/Dev/i18n/pt/React/commands/accessibility-check.md +151 -42
- package/Dev/i18n/pt/React/commands/bundle-analyze.md +300 -25
- package/Dev/i18n/pt/React/commands/check-architecture.md +369 -34
- package/Dev/i18n/pt/React/commands/check-code-quality.md +440 -34
- package/Dev/i18n/pt/React/commands/check-compliance.md +232 -62
- package/Dev/i18n/pt/React/commands/check-security.md +370 -31
- package/Dev/i18n/pt/React/commands/check-testing.md +473 -43
- package/Dev/i18n/pt/React/commands/generate-component.md +454 -19
- package/Dev/i18n/pt/React/commands/generate-hook.md +425 -24
- package/Dev/i18n/pt/React/commands/storybook-story.md +520 -35
- package/Dev/i18n/pt/ReactNative/commands/app-size.md +358 -387
- package/Dev/i18n/pt/ReactNative/commands/check-code-quality.md +246 -75
- package/Dev/i18n/pt/ReactNative/commands/check-compliance.md +191 -85
- package/Dev/i18n/pt/ReactNative/commands/check-security.md +363 -115
- package/Dev/i18n/pt/ReactNative/commands/check-testing.md +269 -79
- package/Dev/i18n/pt/ReactNative/commands/generate-screen.md +491 -324
- package/Dev/i18n/pt/ReactNative/commands/native-module.md +539 -82
- package/Dev/i18n/pt/ReactNative/commands/store-prepare.md +429 -185
- package/Dev/i18n/pt/ReactNative/rules/02-architecture.md +1084 -31
- package/Dev/i18n/pt/Symfony/commands/check-compliance.md +195 -123
- package/Dev/i18n/pt/UIUX/commands/a11y-audit.md +208 -24
- package/Dev/i18n/pt/UIUX/commands/a11y-component.md +281 -23
- package/Dev/i18n/pt/UIUX/commands/design-tokens.md +197 -33
- package/Dev/i18n/pt/UIUX/commands/user-flow.md +183 -20
- package/Dev/i18n/pt/VueJS/commands/check-compliance.md +228 -103
- package/Dev/i18n/pt/Workflow/commands/analyze.md +147 -146
- package/Dev/i18n/pt/Workflow/commands/design.md +205 -204
- package/Dev/i18n/pt/Workflow/commands/implement.md +184 -184
- package/Project/i18n/de/Sprint/commands/dev.md +339 -86
- package/Project/i18n/es/Sprint/commands/dev.md +342 -91
- package/Project/i18n/pt/Sprint/commands/dev.md +343 -92
- package/bundles/cursor/.cursorrules +1 -1
- package/bundles/windsurf/.windsurfrules +1 -1
- package/package.json +1 -1
|
@@ -1,73 +1,561 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: Geração de Widget Flutter com Testes
|
|
3
|
+
argument-hint: [arguments]
|
|
3
4
|
---
|
|
4
5
|
|
|
5
|
-
#
|
|
6
|
+
# Geração de Widget Flutter com Testes
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
Você é um desenvolvedor Flutter sênior. Você deve gerar um widget reutilizável com documentação, testes unitários e widget tests.
|
|
8
9
|
|
|
9
|
-
##
|
|
10
|
+
## Argumentos
|
|
11
|
+
$ARGUMENTS
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
13
|
+
Argumentos:
|
|
14
|
+
- Nome do widget (ex: `CustomButton`, `UserCard`)
|
|
15
|
+
- (Opcional) Tipo (stateless, stateful, hook)
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
Exemplo: `/flutter:generate-widget UserCard stateless`
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
## Plan Mode
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
> **O modo de planejamento é obrigatório.** Antes de executar, o Claude ativa o modo de planejamento para analisar o código impactado, propor um plano de implementação e aguardar sua validação antes de realizar qualquer alteração.
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
## MISSÃO
|
|
22
24
|
|
|
25
|
+
### Etapa 1: Criar o Widget
|
|
26
|
+
|
|
27
|
+
#### StatelessWidget
|
|
23
28
|
```dart
|
|
24
|
-
// lib
|
|
29
|
+
// lib/shared/widgets/{widget_name}.dart
|
|
25
30
|
import 'package:flutter/material.dart';
|
|
26
31
|
|
|
27
|
-
///
|
|
32
|
+
/// Um widget que exibe {description}.
|
|
33
|
+
///
|
|
34
|
+
/// Exemplo de uso:
|
|
35
|
+
/// ```dart
|
|
36
|
+
/// {WidgetName}(
|
|
37
|
+
/// title: 'Meu título',
|
|
38
|
+
/// onTap: () => print('Tapped'),
|
|
39
|
+
/// )
|
|
40
|
+
/// ```
|
|
28
41
|
class {WidgetName} extends StatelessWidget {
|
|
42
|
+
/// O título exibido no widget.
|
|
43
|
+
final String title;
|
|
44
|
+
|
|
45
|
+
/// O subtítulo opcional.
|
|
46
|
+
final String? subtitle;
|
|
47
|
+
|
|
48
|
+
/// O ícone exibido à esquerda.
|
|
49
|
+
final IconData? leadingIcon;
|
|
50
|
+
|
|
51
|
+
/// Callback chamado ao tocar.
|
|
52
|
+
final VoidCallback? onTap;
|
|
53
|
+
|
|
54
|
+
/// Indica se o widget está habilitado.
|
|
55
|
+
final bool isEnabled;
|
|
56
|
+
|
|
57
|
+
/// Cria um novo [{WidgetName}].
|
|
29
58
|
const {WidgetName}({
|
|
30
59
|
super.key,
|
|
31
|
-
required this.
|
|
60
|
+
required this.title,
|
|
61
|
+
this.subtitle,
|
|
62
|
+
this.leadingIcon,
|
|
63
|
+
this.onTap,
|
|
64
|
+
this.isEnabled = true,
|
|
32
65
|
});
|
|
33
66
|
|
|
34
|
-
|
|
67
|
+
@override
|
|
68
|
+
Widget build(BuildContext context) {
|
|
69
|
+
final theme = Theme.of(context);
|
|
70
|
+
|
|
71
|
+
return Semantics(
|
|
72
|
+
button: onTap != null,
|
|
73
|
+
enabled: isEnabled,
|
|
74
|
+
child: Material(
|
|
75
|
+
color: Colors.transparent,
|
|
76
|
+
child: InkWell(
|
|
77
|
+
onTap: isEnabled ? onTap : null,
|
|
78
|
+
borderRadius: BorderRadius.circular(12),
|
|
79
|
+
child: Container(
|
|
80
|
+
padding: const EdgeInsets.all(16),
|
|
81
|
+
decoration: BoxDecoration(
|
|
82
|
+
color: theme.cardColor,
|
|
83
|
+
borderRadius: BorderRadius.circular(12),
|
|
84
|
+
boxShadow: [
|
|
85
|
+
BoxShadow(
|
|
86
|
+
color: Colors.black.withOpacity(0.05),
|
|
87
|
+
blurRadius: 10,
|
|
88
|
+
offset: const Offset(0, 2),
|
|
89
|
+
),
|
|
90
|
+
],
|
|
91
|
+
),
|
|
92
|
+
child: Row(
|
|
93
|
+
children: [
|
|
94
|
+
if (leadingIcon != null) ...[
|
|
95
|
+
Icon(
|
|
96
|
+
leadingIcon,
|
|
97
|
+
color: isEnabled
|
|
98
|
+
? theme.colorScheme.primary
|
|
99
|
+
: theme.disabledColor,
|
|
100
|
+
),
|
|
101
|
+
const SizedBox(width: 12),
|
|
102
|
+
],
|
|
103
|
+
Expanded(
|
|
104
|
+
child: Column(
|
|
105
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
106
|
+
children: [
|
|
107
|
+
Text(
|
|
108
|
+
title,
|
|
109
|
+
style: theme.textTheme.titleMedium?.copyWith(
|
|
110
|
+
color: isEnabled ? null : theme.disabledColor,
|
|
111
|
+
),
|
|
112
|
+
),
|
|
113
|
+
if (subtitle != null) ...[
|
|
114
|
+
const SizedBox(height: 4),
|
|
115
|
+
Text(
|
|
116
|
+
subtitle!,
|
|
117
|
+
style: theme.textTheme.bodySmall?.copyWith(
|
|
118
|
+
color: theme.textTheme.bodySmall?.color
|
|
119
|
+
?.withOpacity(0.7),
|
|
120
|
+
),
|
|
121
|
+
),
|
|
122
|
+
],
|
|
123
|
+
],
|
|
124
|
+
),
|
|
125
|
+
),
|
|
126
|
+
if (onTap != null)
|
|
127
|
+
Icon(
|
|
128
|
+
Icons.chevron_right,
|
|
129
|
+
color: isEnabled
|
|
130
|
+
? theme.colorScheme.onSurface.withOpacity(0.5)
|
|
131
|
+
: theme.disabledColor,
|
|
132
|
+
),
|
|
133
|
+
],
|
|
134
|
+
),
|
|
135
|
+
),
|
|
136
|
+
),
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### StatefulWidget (com animação)
|
|
144
|
+
```dart
|
|
145
|
+
// lib/shared/widgets/{widget_name}.dart
|
|
146
|
+
import 'package:flutter/material.dart';
|
|
147
|
+
|
|
148
|
+
class {WidgetName} extends StatefulWidget {
|
|
149
|
+
final String title;
|
|
150
|
+
final bool isExpanded;
|
|
151
|
+
final Widget child;
|
|
152
|
+
final ValueChanged<bool>? onExpansionChanged;
|
|
153
|
+
|
|
154
|
+
const {WidgetName}({
|
|
155
|
+
super.key,
|
|
156
|
+
required this.title,
|
|
157
|
+
this.isExpanded = false,
|
|
158
|
+
required this.child,
|
|
159
|
+
this.onExpansionChanged,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
@override
|
|
163
|
+
State<{WidgetName}> createState() => _{WidgetName}State();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
class _{WidgetName}State extends State<{WidgetName}>
|
|
167
|
+
with SingleTickerProviderStateMixin {
|
|
168
|
+
late AnimationController _controller;
|
|
169
|
+
late Animation<double> _heightFactor;
|
|
170
|
+
late Animation<double> _iconTurns;
|
|
171
|
+
|
|
172
|
+
bool _isExpanded = false;
|
|
173
|
+
|
|
174
|
+
@override
|
|
175
|
+
void initState() {
|
|
176
|
+
super.initState();
|
|
177
|
+
_isExpanded = widget.isExpanded;
|
|
178
|
+
|
|
179
|
+
_controller = AnimationController(
|
|
180
|
+
duration: const Duration(milliseconds: 200),
|
|
181
|
+
vsync: this,
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
_heightFactor = _controller.drive(CurveTween(curve: Curves.easeIn));
|
|
185
|
+
_iconTurns = _controller.drive(
|
|
186
|
+
Tween<double>(begin: 0.0, end: 0.5).chain(
|
|
187
|
+
CurveTween(curve: Curves.easeIn),
|
|
188
|
+
),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
if (_isExpanded) {
|
|
192
|
+
_controller.value = 1.0;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
@override
|
|
197
|
+
void dispose() {
|
|
198
|
+
_controller.dispose();
|
|
199
|
+
super.dispose();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
void _handleTap() {
|
|
203
|
+
setState(() {
|
|
204
|
+
_isExpanded = !_isExpanded;
|
|
205
|
+
if (_isExpanded) {
|
|
206
|
+
_controller.forward();
|
|
207
|
+
} else {
|
|
208
|
+
_controller.reverse();
|
|
209
|
+
}
|
|
210
|
+
widget.onExpansionChanged?.call(_isExpanded);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
35
213
|
|
|
36
214
|
@override
|
|
37
215
|
Widget build(BuildContext context) {
|
|
38
|
-
|
|
216
|
+
final theme = Theme.of(context);
|
|
217
|
+
|
|
218
|
+
return AnimatedBuilder(
|
|
219
|
+
animation: _controller,
|
|
220
|
+
builder: (context, child) {
|
|
221
|
+
return Column(
|
|
222
|
+
mainAxisSize: MainAxisSize.min,
|
|
223
|
+
children: [
|
|
224
|
+
InkWell(
|
|
225
|
+
onTap: _handleTap,
|
|
226
|
+
child: Padding(
|
|
227
|
+
padding: const EdgeInsets.all(16),
|
|
228
|
+
child: Row(
|
|
229
|
+
children: [
|
|
230
|
+
Expanded(
|
|
231
|
+
child: Text(
|
|
232
|
+
widget.title,
|
|
233
|
+
style: theme.textTheme.titleMedium,
|
|
234
|
+
),
|
|
235
|
+
),
|
|
236
|
+
RotationTransition(
|
|
237
|
+
turns: _iconTurns,
|
|
238
|
+
child: const Icon(Icons.expand_more),
|
|
239
|
+
),
|
|
240
|
+
],
|
|
241
|
+
),
|
|
242
|
+
),
|
|
243
|
+
),
|
|
244
|
+
ClipRect(
|
|
245
|
+
child: Align(
|
|
246
|
+
heightFactor: _heightFactor.value,
|
|
247
|
+
child: child,
|
|
248
|
+
),
|
|
249
|
+
),
|
|
250
|
+
],
|
|
251
|
+
);
|
|
252
|
+
},
|
|
253
|
+
child: widget.child,
|
|
254
|
+
);
|
|
39
255
|
}
|
|
40
256
|
}
|
|
41
257
|
```
|
|
42
258
|
|
|
43
|
-
### 2
|
|
259
|
+
### Etapa 2: Testes
|
|
44
260
|
|
|
261
|
+
#### Widget Test
|
|
45
262
|
```dart
|
|
46
|
-
// test
|
|
263
|
+
// test/shared/widgets/{widget_name}_test.dart
|
|
47
264
|
import 'package:flutter/material.dart';
|
|
48
265
|
import 'package:flutter_test/flutter_test.dart';
|
|
266
|
+
import 'package:app/shared/widgets/{widget_name}.dart';
|
|
49
267
|
|
|
50
268
|
void main() {
|
|
51
269
|
group('{WidgetName}', () {
|
|
52
|
-
testWidgets('
|
|
270
|
+
testWidgets('renders correctly with required props', (tester) async {
|
|
271
|
+
await tester.pumpWidget(
|
|
272
|
+
const MaterialApp(
|
|
273
|
+
home: Scaffold(
|
|
274
|
+
body: {WidgetName}(
|
|
275
|
+
title: 'Test Title',
|
|
276
|
+
),
|
|
277
|
+
),
|
|
278
|
+
),
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
expect(find.text('Test Title'), findsOneWidget);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
testWidgets('renders subtitle when provided', (tester) async {
|
|
285
|
+
await tester.pumpWidget(
|
|
286
|
+
const MaterialApp(
|
|
287
|
+
home: Scaffold(
|
|
288
|
+
body: {WidgetName}(
|
|
289
|
+
title: 'Test Title',
|
|
290
|
+
subtitle: 'Test Subtitle',
|
|
291
|
+
),
|
|
292
|
+
),
|
|
293
|
+
),
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
expect(find.text('Test Subtitle'), findsOneWidget);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
testWidgets('renders leading icon when provided', (tester) async {
|
|
300
|
+
await tester.pumpWidget(
|
|
301
|
+
const MaterialApp(
|
|
302
|
+
home: Scaffold(
|
|
303
|
+
body: {WidgetName}(
|
|
304
|
+
title: 'Test Title',
|
|
305
|
+
leadingIcon: Icons.person,
|
|
306
|
+
),
|
|
307
|
+
),
|
|
308
|
+
),
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
expect(find.byIcon(Icons.person), findsOneWidget);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
testWidgets('calls onTap when tapped', (tester) async {
|
|
315
|
+
var tapped = false;
|
|
316
|
+
|
|
53
317
|
await tester.pumpWidget(
|
|
54
318
|
MaterialApp(
|
|
55
319
|
home: Scaffold(
|
|
56
|
-
body: {WidgetName}(
|
|
320
|
+
body: {WidgetName}(
|
|
321
|
+
title: 'Test Title',
|
|
322
|
+
onTap: () => tapped = true,
|
|
323
|
+
),
|
|
57
324
|
),
|
|
58
325
|
),
|
|
59
326
|
);
|
|
60
327
|
|
|
61
|
-
|
|
328
|
+
await tester.tap(find.byType({WidgetName}));
|
|
329
|
+
await tester.pump();
|
|
330
|
+
|
|
331
|
+
expect(tapped, isTrue);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
testWidgets('does not call onTap when disabled', (tester) async {
|
|
335
|
+
var tapped = false;
|
|
336
|
+
|
|
337
|
+
await tester.pumpWidget(
|
|
338
|
+
MaterialApp(
|
|
339
|
+
home: Scaffold(
|
|
340
|
+
body: {WidgetName}(
|
|
341
|
+
title: 'Test Title',
|
|
342
|
+
onTap: () => tapped = true,
|
|
343
|
+
isEnabled: false,
|
|
344
|
+
),
|
|
345
|
+
),
|
|
346
|
+
),
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
await tester.tap(find.byType({WidgetName}));
|
|
350
|
+
await tester.pump();
|
|
351
|
+
|
|
352
|
+
expect(tapped, isFalse);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
testWidgets('has correct semantics', (tester) async {
|
|
356
|
+
await tester.pumpWidget(
|
|
357
|
+
MaterialApp(
|
|
358
|
+
home: Scaffold(
|
|
359
|
+
body: {WidgetName}(
|
|
360
|
+
title: 'Test Title',
|
|
361
|
+
onTap: () {},
|
|
362
|
+
),
|
|
363
|
+
),
|
|
364
|
+
),
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
final semantics = tester.getSemantics(find.byType({WidgetName}));
|
|
368
|
+
expect(semantics.hasFlag(SemanticsFlag.isButton), isTrue);
|
|
369
|
+
expect(semantics.hasFlag(SemanticsFlag.isEnabled), isTrue);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
testWidgets('applies theme correctly', (tester) async {
|
|
373
|
+
await tester.pumpWidget(
|
|
374
|
+
MaterialApp(
|
|
375
|
+
theme: ThemeData.light(),
|
|
376
|
+
home: const Scaffold(
|
|
377
|
+
body: {WidgetName}(
|
|
378
|
+
title: 'Test Title',
|
|
379
|
+
),
|
|
380
|
+
),
|
|
381
|
+
),
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
// Verificar que o widget utiliza as cores do tema
|
|
385
|
+
final container = tester.widget<Container>(
|
|
386
|
+
find.descendant(
|
|
387
|
+
of: find.byType({WidgetName}),
|
|
388
|
+
matching: find.byType(Container),
|
|
389
|
+
).first,
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
expect(container.decoration, isNotNull);
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
group('{WidgetName} Golden Tests', () {
|
|
397
|
+
testWidgets('matches golden - light theme', (tester) async {
|
|
398
|
+
await tester.pumpWidget(
|
|
399
|
+
MaterialApp(
|
|
400
|
+
theme: ThemeData.light(),
|
|
401
|
+
home: const Scaffold(
|
|
402
|
+
body: Center(
|
|
403
|
+
child: SizedBox(
|
|
404
|
+
width: 300,
|
|
405
|
+
child: {WidgetName}(
|
|
406
|
+
title: 'Test Title',
|
|
407
|
+
subtitle: 'Test Subtitle',
|
|
408
|
+
leadingIcon: Icons.person,
|
|
409
|
+
),
|
|
410
|
+
),
|
|
411
|
+
),
|
|
412
|
+
),
|
|
413
|
+
),
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
await expectLater(
|
|
417
|
+
find.byType({WidgetName}),
|
|
418
|
+
matchesGoldenFile('goldens/{widget_name}_light.png'),
|
|
419
|
+
);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
testWidgets('matches golden - dark theme', (tester) async {
|
|
423
|
+
await tester.pumpWidget(
|
|
424
|
+
MaterialApp(
|
|
425
|
+
theme: ThemeData.dark(),
|
|
426
|
+
home: const Scaffold(
|
|
427
|
+
body: Center(
|
|
428
|
+
child: SizedBox(
|
|
429
|
+
width: 300,
|
|
430
|
+
child: {WidgetName}(
|
|
431
|
+
title: 'Test Title',
|
|
432
|
+
subtitle: 'Test Subtitle',
|
|
433
|
+
leadingIcon: Icons.person,
|
|
434
|
+
),
|
|
435
|
+
),
|
|
436
|
+
),
|
|
437
|
+
),
|
|
438
|
+
),
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
await expectLater(
|
|
442
|
+
find.byType({WidgetName}),
|
|
443
|
+
matchesGoldenFile('goldens/{widget_name}_dark.png'),
|
|
444
|
+
);
|
|
62
445
|
});
|
|
63
446
|
});
|
|
64
447
|
}
|
|
65
448
|
```
|
|
66
449
|
|
|
67
|
-
|
|
450
|
+
### Etapa 3: Exportação
|
|
451
|
+
|
|
452
|
+
```dart
|
|
453
|
+
// lib/shared/widgets/widgets.dart
|
|
454
|
+
export '{widget_name}.dart';
|
|
455
|
+
// Adicionar os outros widgets...
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Etapa 4: Documentação Storybook (opcional)
|
|
459
|
+
|
|
460
|
+
```dart
|
|
461
|
+
// lib/shared/widgets/{widget_name}_stories.dart
|
|
462
|
+
import 'package:flutter/material.dart';
|
|
463
|
+
import '{widget_name}.dart';
|
|
464
|
+
|
|
465
|
+
class {WidgetName}Stories extends StatelessWidget {
|
|
466
|
+
const {WidgetName}Stories({super.key});
|
|
467
|
+
|
|
468
|
+
@override
|
|
469
|
+
Widget build(BuildContext context) {
|
|
470
|
+
return Scaffold(
|
|
471
|
+
appBar: AppBar(title: const Text('{WidgetName} Stories')),
|
|
472
|
+
body: ListView(
|
|
473
|
+
padding: const EdgeInsets.all(16),
|
|
474
|
+
children: [
|
|
475
|
+
_buildStory(
|
|
476
|
+
'Default',
|
|
477
|
+
const {WidgetName}(title: 'Default Title'),
|
|
478
|
+
),
|
|
479
|
+
_buildStory(
|
|
480
|
+
'With Subtitle',
|
|
481
|
+
const {WidgetName}(
|
|
482
|
+
title: 'With Subtitle',
|
|
483
|
+
subtitle: 'This is a subtitle',
|
|
484
|
+
),
|
|
485
|
+
),
|
|
486
|
+
_buildStory(
|
|
487
|
+
'With Icon',
|
|
488
|
+
const {WidgetName}(
|
|
489
|
+
title: 'With Icon',
|
|
490
|
+
leadingIcon: Icons.star,
|
|
491
|
+
),
|
|
492
|
+
),
|
|
493
|
+
_buildStory(
|
|
494
|
+
'Disabled',
|
|
495
|
+
const {WidgetName}(
|
|
496
|
+
title: 'Disabled',
|
|
497
|
+
isEnabled: false,
|
|
498
|
+
),
|
|
499
|
+
),
|
|
500
|
+
_buildStory(
|
|
501
|
+
'Full Example',
|
|
502
|
+
{WidgetName}(
|
|
503
|
+
title: 'Full Example',
|
|
504
|
+
subtitle: 'All props enabled',
|
|
505
|
+
leadingIcon: Icons.person,
|
|
506
|
+
onTap: () {},
|
|
507
|
+
),
|
|
508
|
+
),
|
|
509
|
+
],
|
|
510
|
+
),
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
Widget _buildStory(String name, Widget widget) {
|
|
515
|
+
return Column(
|
|
516
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
517
|
+
children: [
|
|
518
|
+
Text(
|
|
519
|
+
name,
|
|
520
|
+
style: const TextStyle(
|
|
521
|
+
fontSize: 12,
|
|
522
|
+
fontWeight: FontWeight.bold,
|
|
523
|
+
color: Colors.grey,
|
|
524
|
+
),
|
|
525
|
+
),
|
|
526
|
+
const SizedBox(height: 8),
|
|
527
|
+
widget,
|
|
528
|
+
const SizedBox(height: 24),
|
|
529
|
+
],
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### Resumo
|
|
536
|
+
|
|
537
|
+
```
|
|
538
|
+
══════════════════════════════════════════════════════════════
|
|
539
|
+
✅ WIDGET GERADO - {WidgetName}
|
|
540
|
+
══════════════════════════════════════════════════════════════
|
|
68
541
|
|
|
69
|
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
542
|
+
📁 Arquivos criados:
|
|
543
|
+
- lib/shared/widgets/{widget_name}.dart
|
|
544
|
+
- test/shared/widgets/{widget_name}_test.dart
|
|
545
|
+
- test/shared/widgets/goldens/{widget_name}_light.png (a gerar)
|
|
546
|
+
- test/shared/widgets/goldens/{widget_name}_dark.png (a gerar)
|
|
547
|
+
|
|
548
|
+
🔧 Comandos úteis:
|
|
549
|
+
# Executar os testes
|
|
550
|
+
flutter test test/shared/widgets/{widget_name}_test.dart
|
|
551
|
+
|
|
552
|
+
# Atualizar os golden tests
|
|
553
|
+
flutter test --update-goldens
|
|
554
|
+
|
|
555
|
+
📖 Props disponíveis:
|
|
556
|
+
- title (String, required)
|
|
557
|
+
- subtitle (String?)
|
|
558
|
+
- leadingIcon (IconData?)
|
|
559
|
+
- onTap (VoidCallback?)
|
|
560
|
+
- isEnabled (bool, default: true)
|
|
561
|
+
```
|