skills/tdd-workflow/SKILL.md
Test-driven development workflow for React Native — Jest, React Native Testing Library, and Detox
npx skillsauth add JubaKitiashvili/everything-react-native-expo tdd-workflowInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are executing a test-driven development workflow for React Native. Follow the Red-Green-Refactor cycle strictly.
Invoke this skill when:
Before writing any implementation code, write a test that describes the expected behavior:
Component test (React Native Testing Library):
import { render, screen, fireEvent } from '@testing-library/react-native';
import { LoginForm } from '../LoginForm';
describe('LoginForm', () => {
it('disables submit when fields are empty', () => {
render(<LoginForm onSubmit={jest.fn()} />);
const submitButton = screen.getByRole('button', { name: /sign in/i });
expect(submitButton).toBeDisabled();
});
it('calls onSubmit with email and password', () => {
const onSubmit = jest.fn();
render(<LoginForm onSubmit={onSubmit} />);
fireEvent.changeText(screen.getByPlaceholderText(/email/i), '[email protected]');
fireEvent.changeText(screen.getByPlaceholderText(/password/i), 'secret123');
fireEvent.press(screen.getByRole('button', { name: /sign in/i }));
expect(onSubmit).toHaveBeenCalledWith({
email: '[email protected]',
password: 'secret123',
});
});
it('shows error message on failed login', async () => {
const onSubmit = jest.fn().mockRejectedValue(new Error('Invalid credentials'));
render(<LoginForm onSubmit={onSubmit} />);
fireEvent.changeText(screen.getByPlaceholderText(/email/i), '[email protected]');
fireEvent.changeText(screen.getByPlaceholderText(/password/i), 'wrong');
fireEvent.press(screen.getByRole('button', { name: /sign in/i }));
expect(await screen.findByText(/invalid credentials/i)).toBeTruthy();
});
});
Run the test. It MUST fail (red).
Write the minimum code to make the test pass. Do NOT add anything extra:
export function LoginForm({ onSubmit }: LoginFormProps) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState<string | null>(null);
const handleSubmit = async () => {
try {
await onSubmit({ email, password });
} catch (e) {
setError(e instanceof Error ? e.message : 'Unknown error');
}
};
return (
<View>
<TextInput placeholder="Email" value={email} onChangeText={setEmail} />
<TextInput placeholder="Password" value={password} onChangeText={setPassword} secureTextEntry />
<Pressable
onPress={handleSubmit}
disabled={!email || !password}
accessibilityRole="button"
accessibilityLabel="Sign in"
>
<Text>Sign In</Text>
</Pressable>
{error && <Text>{error}</Text>}
</View>
);
}
Run tests again. All MUST pass (green).
Now improve the code without changing behavior:
useLoginForm)Run tests after every change. They MUST stay green.
| Layer | Tool | What to Test | |-------|------|-------------| | Unit | Jest | Pure functions, hooks, utilities | | Component | RNTL | Component rendering, user interactions | | Integration | RNTL | Multiple components working together | | E2E | Detox | Full user flows on real app |
Tests live next to their source:
src/features/auth/
LoginForm.tsx
__tests__/
LoginForm.test.tsx
hooks/
useLoginForm.ts
__tests__/
useLoginForm.test.ts
screen queries — prefer getByRole, getByText, getByPlaceholderTextdevelopment
Guided version migration for React Native and Expo SDK upgrades
development
Mobile security audit for React Native applications
development
Step-by-step performance diagnosis and optimization for React Native apps
documentation
Guided wizard for creating Turbo Modules and Expo Modules with iOS and Android implementations