Help us improve
Share bugs, ideas, or general feedback.
From erne-universal
Guides test-driven development workflow for React Native using Jest, React Native Testing Library, and Detox in Red-Green-Refactor cycle. For new features, bug fixes, refactoring.
npx claudepluginhub jubakitiashvili/everything-react-native-expoHow this skill is triggered — by the user, by Claude, or both
Slash command
/erne-universal:tdd-workflowThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are executing a test-driven development workflow for React Native. Follow the Red-Green-Refactor cycle strictly.
Guides Test-Driven Development for React Native using Jest (jest-expo) and @testing-library/react-native. Use before implementing features, bugfixes, or refactors.
Guides writing and configuring Detox E2E tests for React Native mobile apps, including setup, matchers, actions, and CI integration.
Provides Jest testing patterns including factory functions, mocking strategies, custom renders, and TDD workflow for React Native unit tests.
Share bugs, ideas, or general feedback.
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), 'user@test.com');
fireEvent.changeText(screen.getByPlaceholderText(/password/i), 'secret123');
fireEvent.press(screen.getByRole('button', { name: /sign in/i }));
expect(onSubmit).toHaveBeenCalledWith({
email: 'user@test.com',
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), 'user@test.com');
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, getByPlaceholderText