@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.
Files changed (79) hide show
  1. package/Dev/i18n/de/Angular/commands/check-compliance.md +218 -168
  2. package/Dev/i18n/de/Common/agents/refactoring-specialist.md +146 -69
  3. package/Dev/i18n/de/Common/commands/add-technology.md +166 -68
  4. package/Dev/i18n/de/Common/commands/setup-ci.md +216 -33
  5. package/Dev/i18n/de/Laravel/commands/check-compliance.md +223 -115
  6. package/Dev/i18n/de/Python/checklists/new-feature.md +319 -44
  7. package/Dev/i18n/de/React/rules/02-architecture.md +483 -322
  8. package/Dev/i18n/de/React/rules/03-coding-standards.md +584 -368
  9. package/Dev/i18n/de/Symfony/commands/check-compliance.md +186 -114
  10. package/Dev/i18n/de/Symfony/rules/07-testing-symfony.md +679 -10
  11. package/Dev/i18n/de/UIUX/commands/a11y-component.md +284 -26
  12. package/Dev/i18n/de/UIUX/commands/design-tokens.md +199 -35
  13. package/Dev/i18n/de/UIUX/commands/user-flow.md +179 -16
  14. package/Dev/i18n/de/VueJS/commands/check-compliance.md +227 -102
  15. package/Dev/i18n/es/Angular/commands/check-compliance.md +217 -167
  16. package/Dev/i18n/es/Common/commands/add-technology.md +158 -60
  17. package/Dev/i18n/es/Laravel/commands/check-compliance.md +223 -115
  18. package/Dev/i18n/es/React/commands/check-testing.md +263 -169
  19. package/Dev/i18n/es/ReactNative/commands/check-testing.md +256 -198
  20. package/Dev/i18n/es/Symfony/commands/check-compliance.md +189 -117
  21. package/Dev/i18n/es/Symfony/commands/check-testing.md +301 -102
  22. package/Dev/i18n/es/Symfony/commands/migration-plan.md +267 -34
  23. package/Dev/i18n/es/UIUX/commands/a11y-component.md +274 -16
  24. package/Dev/i18n/es/UIUX/commands/design-tokens.md +186 -22
  25. package/Dev/i18n/es/UIUX/commands/user-flow.md +173 -10
  26. package/Dev/i18n/es/VueJS/commands/check-compliance.md +227 -102
  27. package/Dev/i18n/fr/Angular/commands/check-compliance.md +217 -167
  28. package/Dev/i18n/fr/Common/commands/ralph-run.md +138 -60
  29. package/Dev/i18n/fr/Laravel/commands/check-compliance.md +223 -115
  30. package/Dev/i18n/fr/React/commands/check-architecture.md +370 -53
  31. package/Dev/i18n/fr/React/commands/check-code-quality.md +454 -64
  32. package/Dev/i18n/fr/React/commands/check-testing.md +476 -102
  33. package/Dev/i18n/fr/Symfony/commands/check-compliance.md +188 -116
  34. package/Dev/i18n/fr/VueJS/commands/check-compliance.md +227 -102
  35. package/Dev/i18n/pt/Angular/commands/check-compliance.md +225 -175
  36. package/Dev/i18n/pt/Common/agents/ralph-conductor.md +211 -30
  37. package/Dev/i18n/pt/Common/commands/add-technology.md +149 -51
  38. package/Dev/i18n/pt/Common/commands/ralph-run.md +239 -35
  39. package/Dev/i18n/pt/Flutter/commands/analyze-performance.md +331 -46
  40. package/Dev/i18n/pt/Flutter/commands/check-compliance.md +234 -29
  41. package/Dev/i18n/pt/Flutter/commands/generate-feature.md +526 -51
  42. package/Dev/i18n/pt/Flutter/commands/generate-widget.md +515 -27
  43. package/Dev/i18n/pt/Flutter/commands/golden-update.md +322 -46
  44. package/Dev/i18n/pt/Flutter/commands/localization-check.md +278 -37
  45. package/Dev/i18n/pt/Laravel/commands/check-compliance.md +212 -104
  46. package/Dev/i18n/pt/React/commands/accessibility-check.md +151 -42
  47. package/Dev/i18n/pt/React/commands/bundle-analyze.md +300 -25
  48. package/Dev/i18n/pt/React/commands/check-architecture.md +369 -34
  49. package/Dev/i18n/pt/React/commands/check-code-quality.md +440 -34
  50. package/Dev/i18n/pt/React/commands/check-compliance.md +232 -62
  51. package/Dev/i18n/pt/React/commands/check-security.md +370 -31
  52. package/Dev/i18n/pt/React/commands/check-testing.md +473 -43
  53. package/Dev/i18n/pt/React/commands/generate-component.md +454 -19
  54. package/Dev/i18n/pt/React/commands/generate-hook.md +425 -24
  55. package/Dev/i18n/pt/React/commands/storybook-story.md +520 -35
  56. package/Dev/i18n/pt/ReactNative/commands/app-size.md +358 -387
  57. package/Dev/i18n/pt/ReactNative/commands/check-code-quality.md +246 -75
  58. package/Dev/i18n/pt/ReactNative/commands/check-compliance.md +191 -85
  59. package/Dev/i18n/pt/ReactNative/commands/check-security.md +363 -115
  60. package/Dev/i18n/pt/ReactNative/commands/check-testing.md +269 -79
  61. package/Dev/i18n/pt/ReactNative/commands/generate-screen.md +491 -324
  62. package/Dev/i18n/pt/ReactNative/commands/native-module.md +539 -82
  63. package/Dev/i18n/pt/ReactNative/commands/store-prepare.md +429 -185
  64. package/Dev/i18n/pt/ReactNative/rules/02-architecture.md +1084 -31
  65. package/Dev/i18n/pt/Symfony/commands/check-compliance.md +195 -123
  66. package/Dev/i18n/pt/UIUX/commands/a11y-audit.md +208 -24
  67. package/Dev/i18n/pt/UIUX/commands/a11y-component.md +281 -23
  68. package/Dev/i18n/pt/UIUX/commands/design-tokens.md +197 -33
  69. package/Dev/i18n/pt/UIUX/commands/user-flow.md +183 -20
  70. package/Dev/i18n/pt/VueJS/commands/check-compliance.md +228 -103
  71. package/Dev/i18n/pt/Workflow/commands/analyze.md +147 -146
  72. package/Dev/i18n/pt/Workflow/commands/design.md +205 -204
  73. package/Dev/i18n/pt/Workflow/commands/implement.md +184 -184
  74. package/Project/i18n/de/Sprint/commands/dev.md +339 -86
  75. package/Project/i18n/es/Sprint/commands/dev.md +342 -91
  76. package/Project/i18n/pt/Sprint/commands/dev.md +343 -92
  77. package/bundles/cursor/.cursorrules +1 -1
  78. package/bundles/windsurf/.windsurfrules +1 -1
  79. package/package.json +1 -1
@@ -1,420 +1,587 @@
1
1
  ---
2
- description: Comando: Gerar Screen
2
+ description: Geração de Tela React Native
3
+ argument-hint: [arguments]
3
4
  ---
4
5
 
5
- # Comando: Gerar Screen
6
+ # Geração de Tela React Native
6
7
 
7
- Gere uma nova screen React Native com Expo Router seguindo as melhores práticas e padrões do projeto.
8
+ Você é um desenvolvedor React Native sênior. Você deve gerar uma tela completa com navegação, gerenciamento de estado, testes e acessibilidade.
8
9
 
9
- ---
10
-
11
- ## Uso
10
+ ## Argumentos
11
+ $ARGUMENTS
12
12
 
13
- ```bash
14
- npx generate-screen <nome-da-screen> [opções]
15
- ```
13
+ Argumentos:
14
+ - Nome da tela (ex.: `Profile`, `Settings`, `ProductDetail`)
15
+ - (Opcional) Tipo: list, detail, form, dashboard
16
16
 
17
- **Opções:**
18
- - `--type`: tipo de screen (stack, tab, modal)
19
- - `--feature`: feature a qual pertence
20
- - `--with-test`: gerar arquivo de teste
21
- - `--with-form`: incluir componentes de formulário
17
+ Exemplo: `/reactnative:generate-screen ProductDetail detail`
22
18
 
23
- **Exemplos:**
19
+ ## Modo de Planejamento
24
20
 
25
- ```bash
26
- # Screen simples
27
- npx generate-screen UserProfile
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.
28
22
 
29
- # Screen em feature específica
30
- npx generate-screen CreatePost --feature=posts
23
+ ## MISSÃO
31
24
 
32
- # Screen com teste
33
- npx generate-screen Settings --with-test
25
+ ### Etapa 1: Estrutura da Tela
34
26
 
35
- # Modal screen
36
- npx generate-screen ShareModal --type=modal
27
+ ```
28
+ src/
29
+ └── screens/
30
+ └── {ScreenName}/
31
+ ├── index.ts
32
+ ├── {ScreenName}Screen.tsx
33
+ ├── {ScreenName}Screen.types.ts
34
+ ├── {ScreenName}Screen.styles.ts
35
+ ├── {ScreenName}Screen.test.tsx
36
+ ├── hooks/
37
+ │ └── use{ScreenName}.ts
38
+ └── components/
39
+ └── {ScreenName}Header.tsx
37
40
  ```
38
41
 
39
- ---
40
-
41
- ## Modo Plano
42
+ ### Etapa 2: Tipos
42
43
 
43
- > **O modo plano é obrigatório.** Antes de executar, Claude ativa o modo plano para analisar o código impactado, propor um plano de implementação e aguardar sua validação antes de realizar qualquer alteração.
44
+ ```typescript
45
+ // src/screens/{ScreenName}/{ScreenName}Screen.types.ts
46
+ import type { NativeStackScreenProps } from '@react-navigation/native-stack';
47
+ import type { RootStackParamList } from '@/navigation/types';
48
+
49
+ export type {ScreenName}ScreenProps = NativeStackScreenProps<
50
+ RootStackParamList,
51
+ '{ScreenName}'
52
+ >;
53
+
54
+ export interface {ScreenName}ScreenParams {
55
+ id: string;
56
+ title?: string;
57
+ }
44
58
 
45
- ## Templates Gerados
59
+ export interface {ScreenName}Data {
60
+ id: string;
61
+ title: string;
62
+ description: string;
63
+ imageUrl?: string;
64
+ createdAt: string;
65
+ // ...outros campos
66
+ }
46
67
 
47
- ### 1. Screen Básica
68
+ export interface {ScreenName}ScreenState {
69
+ data: {ScreenName}Data | null;
70
+ isLoading: boolean;
71
+ error: string | null;
72
+ }
73
+ ```
48
74
 
49
- **app/user-profile.tsx:**
75
+ ### Etapa 3: Hook Personalizado
50
76
 
51
77
  ```typescript
52
- import { View, Text, StyleSheet } from 'react-native';
53
- import { SafeAreaView } from 'react-native-safe-area-context';
78
+ // src/screens/{ScreenName}/hooks/use{ScreenName}.ts
79
+ import { useState, useEffect, useCallback } from 'react';
80
+ import { useRoute } from '@react-navigation/native';
81
+
82
+ import { {screenName}Service } from '@/services/{screenName}Service';
83
+ import type { {ScreenName}Data, {ScreenName}ScreenParams } from '../{ScreenName}Screen.types';
84
+
85
+ interface Use{ScreenName}Return {
86
+ data: {ScreenName}Data | null;
87
+ isLoading: boolean;
88
+ error: string | null;
89
+ refresh: () => Promise<void>;
90
+ }
54
91
 
55
- export default function UserProfileScreen() {
56
- return (
57
- <SafeAreaView style={styles.container}>
58
- <View style={styles.content}>
59
- <Text style={styles.title}>User Profile</Text>
60
- </View>
61
- </SafeAreaView>
62
- );
92
+ export function use{ScreenName}(): Use{ScreenName}Return {
93
+ const route = useRoute<{ params: {ScreenName}ScreenParams }>();
94
+ const { id } = route.params;
95
+
96
+ const [data, setData] = useState<{ScreenName}Data | null>(null);
97
+ const [isLoading, setIsLoading] = useState(true);
98
+ const [error, setError] = useState<string | null>(null);
99
+
100
+ const fetchData = useCallback(async () => {
101
+ try {
102
+ setIsLoading(true);
103
+ setError(null);
104
+ const result = await {screenName}Service.getById(id);
105
+ setData(result);
106
+ } catch (err) {
107
+ setError(err instanceof Error ? err.message : 'Ocorreu um erro');
108
+ } finally {
109
+ setIsLoading(false);
110
+ }
111
+ }, [id]);
112
+
113
+ useEffect(() => {
114
+ fetchData();
115
+ }, [fetchData]);
116
+
117
+ const refresh = useCallback(async () => {
118
+ await fetchData();
119
+ }, [fetchData]);
120
+
121
+ return { data, isLoading, error, refresh };
63
122
  }
123
+ ```
64
124
 
65
- const styles = StyleSheet.create({
125
+ ### Etapa 4: Estilos
126
+
127
+ ```typescript
128
+ // src/screens/{ScreenName}/{ScreenName}Screen.styles.ts
129
+ import { StyleSheet } from 'react-native';
130
+ import { theme } from '@/theme';
131
+
132
+ export const styles = StyleSheet.create({
66
133
  container: {
67
134
  flex: 1,
68
- backgroundColor: '#fff',
135
+ backgroundColor: theme.colors.background,
69
136
  },
70
137
  content: {
71
138
  flex: 1,
72
- padding: 16,
139
+ padding: theme.spacing.md,
140
+ },
141
+ loadingContainer: {
142
+ flex: 1,
143
+ justifyContent: 'center',
144
+ alignItems: 'center',
145
+ },
146
+ errorContainer: {
147
+ flex: 1,
148
+ justifyContent: 'center',
149
+ alignItems: 'center',
150
+ padding: theme.spacing.lg,
151
+ },
152
+ errorText: {
153
+ fontSize: theme.typography.body.fontSize,
154
+ color: theme.colors.error,
155
+ textAlign: 'center',
156
+ marginBottom: theme.spacing.md,
157
+ },
158
+ retryButton: {
159
+ paddingHorizontal: theme.spacing.lg,
160
+ paddingVertical: theme.spacing.sm,
161
+ backgroundColor: theme.colors.primary,
162
+ borderRadius: theme.borderRadius.md,
163
+ },
164
+ retryButtonText: {
165
+ color: theme.colors.white,
166
+ fontWeight: '600',
167
+ },
168
+ headerImage: {
169
+ width: '100%',
170
+ height: 200,
171
+ resizeMode: 'cover',
73
172
  },
74
173
  title: {
75
- fontSize: 24,
76
- fontWeight: 'bold',
77
- marginBottom: 16,
174
+ fontSize: theme.typography.h1.fontSize,
175
+ fontWeight: theme.typography.h1.fontWeight,
176
+ color: theme.colors.text,
177
+ marginBottom: theme.spacing.sm,
178
+ },
179
+ description: {
180
+ fontSize: theme.typography.body.fontSize,
181
+ color: theme.colors.textSecondary,
182
+ lineHeight: 24,
183
+ },
184
+ metadata: {
185
+ flexDirection: 'row',
186
+ alignItems: 'center',
187
+ marginTop: theme.spacing.md,
188
+ },
189
+ metadataText: {
190
+ fontSize: theme.typography.caption.fontSize,
191
+ color: theme.colors.textMuted,
78
192
  },
79
193
  });
80
194
  ```
81
195
 
82
- ### 2. Screen com Data Fetching
196
+ ### Etapa 5: Componente da Tela
197
+
198
+ ```tsx
199
+ // src/screens/{ScreenName}/{ScreenName}Screen.tsx
200
+ import React, { useCallback } from 'react';
201
+ import {
202
+ View,
203
+ Text,
204
+ ScrollView,
205
+ Image,
206
+ RefreshControl,
207
+ TouchableOpacity,
208
+ ActivityIndicator,
209
+ } from 'react-native';
210
+ import { SafeAreaView } from 'react-native-safe-area-context';
83
211
 
84
- **app/article/[id].tsx:**
212
+ import { use{ScreenName} } from './hooks/use{ScreenName}';
213
+ import { styles } from './{ScreenName}Screen.styles';
214
+ import type { {ScreenName}ScreenProps } from './{ScreenName}Screen.types';
85
215
 
86
- ```typescript
87
- import { View, Text, StyleSheet, ActivityIndicator } from 'react-native';
88
- import { useLocalSearchParams } from 'expo-router';
89
- import { SafeAreaView } from 'react-native-safe-area-context';
90
- import { useArticle } from '@/features/articles/hooks/useArticle';
216
+ export function {ScreenName}Screen({ navigation }: {ScreenName}ScreenProps) {
217
+ const { data, isLoading, error, refresh } = use{ScreenName}();
218
+ const [isRefreshing, setIsRefreshing] = React.useState(false);
91
219
 
92
- export default function ArticleScreen() {
93
- const { id } = useLocalSearchParams<{ id: string }>();
94
- const { data: article, isLoading, error } = useArticle(id);
220
+ const handleRefresh = useCallback(async () => {
221
+ setIsRefreshing(true);
222
+ await refresh();
223
+ setIsRefreshing(false);
224
+ }, [refresh]);
95
225
 
96
- if (isLoading) {
226
+ // Estado de carregamento inicial
227
+ if (isLoading && !data) {
97
228
  return (
98
- <View style={styles.centerContainer}>
99
- <ActivityIndicator size="large" />
100
- </View>
229
+ <SafeAreaView style={styles.container}>
230
+ <View
231
+ style={styles.loadingContainer}
232
+ accessibilityLabel="Carregamento em andamento"
233
+ accessibilityRole="progressbar"
234
+ >
235
+ <ActivityIndicator size="large" />
236
+ </View>
237
+ </SafeAreaView>
101
238
  );
102
239
  }
103
240
 
104
- if (error || !article) {
241
+ // Estado de erro
242
+ if (error && !data) {
105
243
  return (
106
- <View style={styles.centerContainer}>
107
- <Text style={styles.errorText}>Erro ao carregar artigo</Text>
108
- </View>
244
+ <SafeAreaView style={styles.container}>
245
+ <View style={styles.errorContainer}>
246
+ <Text
247
+ style={styles.errorText}
248
+ accessibilityRole="alert"
249
+ >
250
+ {error}
251
+ </Text>
252
+ <TouchableOpacity
253
+ style={styles.retryButton}
254
+ onPress={refresh}
255
+ accessibilityLabel="Tentar novamente o carregamento"
256
+ accessibilityRole="button"
257
+ >
258
+ <Text style={styles.retryButtonText}>Tentar novamente</Text>
259
+ </TouchableOpacity>
260
+ </View>
261
+ </SafeAreaView>
109
262
  );
110
263
  }
111
264
 
265
+ // Estado normal com dados
112
266
  return (
113
- <SafeAreaView style={styles.container}>
114
- <View style={styles.content}>
115
- <Text style={styles.title}>{article.title}</Text>
116
- <Text style={styles.body}>{article.content}</Text>
117
- </View>
267
+ <SafeAreaView style={styles.container} edges={['bottom']}>
268
+ <ScrollView
269
+ style={styles.content}
270
+ refreshControl={
271
+ <RefreshControl
272
+ refreshing={isRefreshing}
273
+ onRefresh={handleRefresh}
274
+ accessibilityLabel="Atualizar página"
275
+ />
276
+ }
277
+ contentContainerStyle={{ paddingBottom: 20 }}
278
+ >
279
+ {data?.imageUrl && (
280
+ <Image
281
+ source={{ uri: data.imageUrl }}
282
+ style={styles.headerImage}
283
+ accessibilityLabel={`Imagem de ${data.title}`}
284
+ />
285
+ )}
286
+
287
+ <View style={{ padding: 16 }}>
288
+ <Text
289
+ style={styles.title}
290
+ accessibilityRole="header"
291
+ >
292
+ {data?.title}
293
+ </Text>
294
+
295
+ <Text style={styles.description}>
296
+ {data?.description}
297
+ </Text>
298
+
299
+ <View style={styles.metadata}>
300
+ <Text style={styles.metadataText}>
301
+ Criado em {new Date(data?.createdAt || '').toLocaleDateString()}
302
+ </Text>
303
+ </View>
304
+ </View>
305
+ </ScrollView>
118
306
  </SafeAreaView>
119
307
  );
120
308
  }
121
-
122
- const styles = StyleSheet.create({
123
- container: {
124
- flex: 1,
125
- backgroundColor: '#fff',
126
- },
127
- centerContainer: {
128
- flex: 1,
129
- justifyContent: 'center',
130
- alignItems: 'center',
131
- },
132
- content: {
133
- flex: 1,
134
- padding: 16,
135
- },
136
- title: {
137
- fontSize: 24,
138
- fontWeight: 'bold',
139
- marginBottom: 16,
140
- },
141
- body: {
142
- fontSize: 16,
143
- lineHeight: 24,
144
- },
145
- errorText: {
146
- fontSize: 16,
147
- color: '#f00',
148
- },
149
- });
150
309
  ```
151
310
 
152
- ### 3. Screen com Formulário
311
+ ### Etapa 6: Exportação
312
+
313
+ ```typescript
314
+ // src/screens/{ScreenName}/index.ts
315
+ export { {ScreenName}Screen } from './{ScreenName}Screen';
316
+ export type { {ScreenName}ScreenProps, {ScreenName}ScreenParams } from './{ScreenName}Screen.types';
317
+ ```
153
318
 
154
- **app/create-post.tsx:**
319
+ ### Etapa 7: Navegação
155
320
 
156
321
  ```typescript
157
- import { View, Text, StyleSheet, ScrollView } from 'react-native';
158
- import { SafeAreaView } from 'react-native-safe-area-context';
159
- import { router } from 'expo-router';
160
- import { useCreatePost } from '@/features/posts/hooks/useCreatePost';
161
- import { PostForm } from '@/features/posts/components/PostForm';
162
- import type { CreatePostDTO } from '@/features/posts/types/Post.types';
163
-
164
- export default function CreatePostScreen() {
165
- const { mutate: createPost, isPending } = useCreatePost();
166
-
167
- const handleSubmit = (data: CreatePostDTO) => {
168
- createPost(data, {
169
- onSuccess: (post) => {
170
- router.push(`/post/${post.id}`);
171
- },
172
- onError: (error) => {
173
- Alert.alert('Erro', 'Não foi possível criar o post');
174
- },
175
- });
176
- };
322
+ // src/navigation/types.ts
323
+ export type RootStackParamList = {
324
+ Home: undefined;
325
+ {ScreenName}: { id: string; title?: string };
326
+ // ...outras telas
327
+ };
328
+
329
+ // src/navigation/RootNavigator.tsx
330
+ import { {ScreenName}Screen } from '@/screens/{ScreenName}';
331
+
332
+ <Stack.Screen
333
+ name="{ScreenName}"
334
+ component={{ScreenName}Screen}
335
+ options={({ route }) => ({
336
+ title: route.params?.title || '{ScreenName}',
337
+ headerBackTitleVisible: false,
338
+ })}
339
+ />
340
+ ```
177
341
 
178
- return (
179
- <SafeAreaView style={styles.container}>
180
- <ScrollView style={styles.scrollView}>
181
- <View style={styles.content}>
182
- <Text style={styles.title}>Criar Novo Post</Text>
183
- <PostForm
184
- onSubmit={handleSubmit}
185
- isSubmitting={isPending}
186
- />
187
- </View>
188
- </ScrollView>
189
- </SafeAreaView>
342
+ ### Etapa 8: Testes
343
+
344
+ ```tsx
345
+ // src/screens/{ScreenName}/{ScreenName}Screen.test.tsx
346
+ import React from 'react';
347
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react-native';
348
+ import { NavigationContainer } from '@react-navigation/native';
349
+ import { createNativeStackNavigator } from '@react-navigation/native-stack';
350
+
351
+ import { {ScreenName}Screen } from './{ScreenName}Screen';
352
+ import { {screenName}Service } from '@/services/{screenName}Service';
353
+
354
+ // Mock do serviço
355
+ jest.mock('@/services/{screenName}Service');
356
+ const mockService = {screenName}Service as jest.Mocked<typeof {screenName}Service>;
357
+
358
+ const Stack = createNativeStackNavigator();
359
+
360
+ function renderWithNavigation(params = { id: '123' }) {
361
+ return render(
362
+ <NavigationContainer>
363
+ <Stack.Navigator>
364
+ <Stack.Screen
365
+ name="{ScreenName}"
366
+ component={{ScreenName}Screen}
367
+ initialParams={params}
368
+ />
369
+ </Stack.Navigator>
370
+ </NavigationContainer>
190
371
  );
191
372
  }
192
373
 
193
- const styles = StyleSheet.create({
194
- container: {
195
- flex: 1,
196
- backgroundColor: '#fff',
197
- },
198
- scrollView: {
199
- flex: 1,
200
- },
201
- content: {
202
- padding: 16,
203
- },
204
- title: {
205
- fontSize: 24,
206
- fontWeight: 'bold',
207
- marginBottom: 24,
208
- },
209
- });
210
- ```
374
+ describe('{ScreenName}Screen', () => {
375
+ beforeEach(() => {
376
+ jest.clearAllMocks();
377
+ });
211
378
 
212
- ### 4. Modal Screen
379
+ describe('Estado de carregamento', () => {
380
+ it('exibe indicador de carregamento inicialmente', () => {
381
+ mockService.getById.mockImplementation(() => new Promise(() => {}));
213
382
 
214
- **app/modal.tsx:**
383
+ renderWithNavigation();
215
384
 
216
- ```typescript
217
- import { View, Text, StyleSheet, Pressable } from 'react-native';
218
- import { router } from 'expo-router';
219
- import { StatusBar } from 'expo-status-bar';
385
+ expect(screen.getByLabelText('Carregamento em andamento')).toBeTruthy();
386
+ });
387
+ });
220
388
 
221
- export default function ModalScreen() {
222
- return (
223
- <View style={styles.container}>
224
- <StatusBar style="light" />
389
+ describe('Estado de sucesso', () => {
390
+ const mockData = {
391
+ id: '123',
392
+ title: 'Título de Teste',
393
+ description: 'Descrição de Teste',
394
+ createdAt: '2024-01-15T10:00:00Z',
395
+ };
225
396
 
226
- <View style={styles.content}>
227
- <Text style={styles.title}>Modal</Text>
397
+ beforeEach(() => {
398
+ mockService.getById.mockResolvedValue(mockData);
399
+ });
228
400
 
229
- <Pressable
230
- style={styles.closeButton}
231
- onPress={() => router.back()}
232
- >
233
- <Text style={styles.closeButtonText}>Fechar</Text>
234
- </Pressable>
235
- </View>
236
- </View>
237
- );
238
- }
401
+ it('exibe os dados corretamente', async () => {
402
+ renderWithNavigation();
239
403
 
240
- const styles = StyleSheet.create({
241
- container: {
242
- flex: 1,
243
- backgroundColor: 'rgba(0, 0, 0, 0.5)',
244
- justifyContent: 'center',
245
- alignItems: 'center',
246
- },
247
- content: {
248
- backgroundColor: '#fff',
249
- borderRadius: 16,
250
- padding: 24,
251
- width: '80%',
252
- maxWidth: 400,
253
- },
254
- title: {
255
- fontSize: 24,
256
- fontWeight: 'bold',
257
- marginBottom: 24,
258
- textAlign: 'center',
259
- },
260
- closeButton: {
261
- backgroundColor: '#007AFF',
262
- padding: 12,
263
- borderRadius: 8,
264
- alignItems: 'center',
265
- },
266
- closeButtonText: {
267
- color: '#fff',
268
- fontSize: 16,
269
- fontWeight: '600',
270
- },
271
- });
272
- ```
404
+ await waitFor(() => {
405
+ expect(screen.getByText('Título de Teste')).toBeTruthy();
406
+ });
273
407
 
274
- ### 5. List Screen
408
+ expect(screen.getByText('Descrição de Teste')).toBeTruthy();
409
+ });
275
410
 
276
- **app/users.tsx:**
411
+ it('suporta pull to refresh', async () => {
412
+ renderWithNavigation();
277
413
 
278
- ```typescript
279
- import { View, Text, StyleSheet, FlatList, ActivityIndicator } from 'react-native';
280
- import { SafeAreaView } from 'react-native-safe-area-context';
281
- import { useUsers } from '@/features/users/hooks/useUsers';
282
- import { UserCard } from '@/features/users/components/UserCard';
283
- import type { User } from '@/features/users/types/User.types';
414
+ await waitFor(() => {
415
+ expect(screen.getByText('Título de Teste')).toBeTruthy();
416
+ });
284
417
 
285
- export default function UsersScreen() {
286
- const { data: users, isLoading, error, refetch } = useUsers();
418
+ const scrollView = screen.getByLabelText('Atualizar página');
419
+ fireEvent(scrollView, 'refresh');
287
420
 
288
- if (isLoading) {
289
- return (
290
- <View style={styles.centerContainer}>
291
- <ActivityIndicator size="large" />
292
- </View>
293
- );
294
- }
421
+ await waitFor(() => {
422
+ expect(mockService.getById).toHaveBeenCalledTimes(2);
423
+ });
424
+ });
425
+ });
295
426
 
296
- if (error) {
297
- return (
298
- <View style={styles.centerContainer}>
299
- <Text style={styles.errorText}>Erro ao carregar usuários</Text>
300
- </View>
301
- );
302
- }
427
+ describe('Estado de erro', () => {
428
+ beforeEach(() => {
429
+ mockService.getById.mockRejectedValue(new Error('Erro de rede'));
430
+ });
303
431
 
304
- return (
305
- <SafeAreaView style={styles.container}>
306
- <FlatList
307
- data={users}
308
- keyExtractor={(item: User) => item.id}
309
- renderItem={({ item }) => <UserCard user={item} />}
310
- contentContainerStyle={styles.listContent}
311
- onRefresh={refetch}
312
- refreshing={isLoading}
313
- ListEmptyComponent={
314
- <View style={styles.emptyContainer}>
315
- <Text style={styles.emptyText}>Nenhum usuário encontrado</Text>
316
- </View>
317
- }
318
- />
319
- </SafeAreaView>
320
- );
321
- }
432
+ it('exibe mensagem de erro', async () => {
433
+ renderWithNavigation();
322
434
 
323
- const styles = StyleSheet.create({
324
- container: {
325
- flex: 1,
326
- backgroundColor: '#fff',
327
- },
328
- centerContainer: {
329
- flex: 1,
330
- justifyContent: 'center',
331
- alignItems: 'center',
332
- },
333
- listContent: {
334
- padding: 16,
335
- },
336
- emptyContainer: {
337
- padding: 32,
338
- alignItems: 'center',
339
- },
340
- emptyText: {
341
- fontSize: 16,
342
- color: '#999',
343
- },
344
- errorText: {
345
- fontSize: 16,
346
- color: '#f00',
347
- },
348
- });
349
- ```
435
+ await waitFor(() => {
436
+ expect(screen.getByText('Erro de rede')).toBeTruthy();
437
+ });
438
+ });
350
439
 
351
- ---
440
+ it('permite tentar novamente em caso de erro', async () => {
441
+ renderWithNavigation();
352
442
 
353
- ## Arquivo de Teste
443
+ await waitFor(() => {
444
+ expect(screen.getByText('Erro de rede')).toBeTruthy();
445
+ });
354
446
 
355
- **app/user-profile.test.tsx:**
447
+ // Agora a nova tentativa será bem-sucedida
448
+ mockService.getById.mockResolvedValueOnce({
449
+ id: '123',
450
+ title: 'Sucesso',
451
+ description: 'Agora funciona',
452
+ createdAt: '2024-01-15',
453
+ });
356
454
 
357
- ```typescript
358
- import { render, screen } from '@testing-library/react-native';
359
- import UserProfileScreen from './user-profile';
455
+ fireEvent.press(screen.getByText('Tentar novamente'));
360
456
 
361
- describe('UserProfileScreen', () => {
362
- it('should render correctly', () => {
363
- render(<UserProfileScreen />);
364
- expect(screen.getByText('User Profile')).toBeTruthy();
457
+ await waitFor(() => {
458
+ expect(screen.getByText('Sucesso')).toBeTruthy();
459
+ });
460
+ });
365
461
  });
366
462
 
367
- it('should match snapshot', () => {
368
- const tree = render(<UserProfileScreen />).toJSON();
369
- expect(tree).toMatchSnapshot();
463
+ describe('Acessibilidade', () => {
464
+ it('possui roles de acessibilidade corretas', async () => {
465
+ mockService.getById.mockResolvedValue({
466
+ id: '123',
467
+ title: 'Teste',
468
+ description: 'Desc',
469
+ createdAt: '2024-01-15',
470
+ });
471
+
472
+ renderWithNavigation();
473
+
474
+ await waitFor(() => {
475
+ expect(screen.getByRole('header', { name: 'Teste' })).toBeTruthy();
476
+ });
477
+ });
370
478
  });
371
479
  });
372
480
  ```
373
481
 
374
- ---
482
+ ### Etapa 9: Variantes de Tela
375
483
 
376
- ## Melhores Práticas
484
+ #### Tela de Lista
377
485
 
378
- ### 1. Naming
379
- - Arquivos: kebab-case (user-profile.tsx)
380
- - Componentes: PascalCase (UserProfileScreen)
381
- - Rotas dinâmicas: [param].tsx
486
+ ```tsx
487
+ // Para uma tela do tipo lista
488
+ import { FlatList } from 'react-native';
382
489
 
383
- ### 2. Estrutura
384
- - SafeAreaView para gerenciar safe areas
385
- - ScrollView para conteúdo que pode rolar
386
- - FlatList para listas longas
387
- - StatusBar configurada apropriadamente
490
+ export function {ScreenName}ListScreen() {
491
+ const { data, isLoading, refresh, loadMore, hasMore } = use{ScreenName}List();
388
492
 
389
- ### 3. Estados
390
- - Loading state
391
- - Error state
392
- - Empty state
393
- - Success state
493
+ return (
494
+ <SafeAreaView style={styles.container}>
495
+ <FlatList
496
+ data={data}
497
+ keyExtractor={(item) => item.id}
498
+ renderItem={({ item }) => (
499
+ <{ScreenName}ListItem item={item} onPress={() => navigate(item.id)} />
500
+ )}
501
+ refreshControl={
502
+ <RefreshControl refreshing={isLoading} onRefresh={refresh} />
503
+ }
504
+ onEndReached={hasMore ? loadMore : undefined}
505
+ onEndReachedThreshold={0.5}
506
+ ListEmptyComponent={<EmptyState message="Nenhum item" />}
507
+ ListFooterComponent={hasMore ? <ActivityIndicator /> : null}
508
+ />
509
+ </SafeAreaView>
510
+ );
511
+ }
512
+ ```
394
513
 
395
- ### 4. Navegação
396
- - useLocalSearchParams para parâmetros de rota
397
- - router.push para navegação
398
- - router.back para voltar
514
+ #### Tela de Formulário
399
515
 
400
- ### 5. Tipagem
401
- - Todas as props tipadas
402
- - Parâmetros de rota tipados
403
- - Data fetching tipado
516
+ ```tsx
517
+ // Para uma tela do tipo formulário
518
+ import { useForm, Controller } from 'react-hook-form';
519
+ import { zodResolver } from '@hookform/resolvers/zod';
404
520
 
405
- ---
521
+ export function {ScreenName}FormScreen() {
522
+ const { control, handleSubmit, formState } = useForm({
523
+ resolver: zodResolver(schema),
524
+ defaultValues: { name: '', email: '' },
525
+ });
406
526
 
407
- ## Checklist
527
+ const onSubmit = async (data) => {
528
+ // ...
529
+ };
408
530
 
409
- - [ ] Screen criada na estrutura de app/
410
- - [ ] SafeAreaView implementada
411
- - [ ] Estados de loading/error tratados
412
- - [ ] Tipagem TypeScript completa
413
- - [ ] Estilos com StyleSheet.create
414
- - [ ] Navegação implementada
415
- - [ ] Teste criado
416
- - [ ] Documentação adicionada
531
+ return (
532
+ <KeyboardAvoidingView behavior="padding" style={styles.container}>
533
+ <ScrollView>
534
+ <Controller
535
+ control={control}
536
+ name="name"
537
+ render={({ field, fieldState }) => (
538
+ <TextInput
539
+ {...field}
540
+ onChangeText={field.onChange}
541
+ placeholder="Nome"
542
+ error={fieldState.error?.message}
543
+ />
544
+ )}
545
+ />
546
+ {/* outros campos */}
547
+ <Button
548
+ title="Salvar"
549
+ onPress={handleSubmit(onSubmit)}
550
+ loading={formState.isSubmitting}
551
+ />
552
+ </ScrollView>
553
+ </KeyboardAvoidingView>
554
+ );
555
+ }
556
+ ```
417
557
 
418
- ---
558
+ ### Resumo
419
559
 
420
- **Screens bem estruturadas são a base de uma boa UX. Siga os padrões consistentemente.**
560
+ ```
561
+ ══════════════════════════════════════════════════════════════
562
+ ✅ TELA GERADA - {ScreenName}
563
+ ══════════════════════════════════════════════════════════════
564
+
565
+ 📁 Arquivos criados:
566
+ - src/screens/{ScreenName}/index.ts
567
+ - src/screens/{ScreenName}/{ScreenName}Screen.tsx
568
+ - src/screens/{ScreenName}/{ScreenName}Screen.types.ts
569
+ - src/screens/{ScreenName}/{ScreenName}Screen.styles.ts
570
+ - src/screens/{ScreenName}/{ScreenName}Screen.test.tsx
571
+ - src/screens/{ScreenName}/hooks/use{ScreenName}.ts
572
+
573
+ 📝 Funcionalidades:
574
+ - TypeScript strict
575
+ - Navegação tipada
576
+ - Hook personalizado para lógica
577
+ - Pull-to-refresh
578
+ - Tratamento de estados: carregamento/erro/sucesso
579
+ - Acessibilidade (roles, labels)
580
+ - Testes completos
581
+
582
+ 🔧 Próximos passos:
583
+ 1. Adicionar a tela à navegação
584
+ 2. Criar serviço de API
585
+ 3. Personalizar estilos
586
+ 4. Executar testes: npm test {ScreenName}
587
+ ```