.claude/skills/tdd-workflow/SKILL.md
TDD patterns for React Native — Jest setup, React Native Testing Library patterns, mock strategies, and what to test vs skip. Auto-load when writing or running tests.
npx skillsauth add taewoongheo/taste 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.
Co-locate tests with source:
components/
Button.tsx
Button.test.tsx # unit test next to component
hooks/
useAuth.ts
useAuth.test.ts
Or use __tests__/ for integration tests:
__tests__/
flows/
login.test.tsx # multi-screen flow tests
| Test | Don't Test |
|------|-----------|
| User-visible behavior | Implementation details |
| Button press triggers action | useState was called |
| Error message appears on failure | Internal state shape |
| List renders correct items | Component re-render count |
| Hook returns expected values | Reanimated worklet internals |
| Navigation on action | Animation physics values |
import { render, fireEvent, waitFor, screen } from '@testing-library/react-native';
describe('SubmitButton', () => {
it('calls onPress when tapped', () => {
const onPress = jest.fn();
render(<SubmitButton onPress={onPress} label="Submit" />);
fireEvent.press(screen.getByText('Submit'));
expect(onPress).toHaveBeenCalledTimes(1);
});
it('shows loading indicator when loading', () => {
render(<SubmitButton onPress={jest.fn()} label="Submit" loading />);
expect(screen.getByTestId('loading-indicator')).toBeTruthy();
expect(screen.queryByText('Submit')).toBeNull();
});
it('does not call onPress when disabled', () => {
const onPress = jest.fn();
render(<SubmitButton onPress={onPress} label="Submit" disabled />);
fireEvent.press(screen.getByText('Submit'));
expect(onPress).not.toHaveBeenCalled();
});
});
import { renderHook, act } from '@testing-library/react-native';
describe('useCounter', () => {
it('starts at initial value', () => {
const { result } = renderHook(() => useCounter(5));
expect(result.current.count).toBe(5);
});
it('increments', () => {
const { result } = renderHook(() => useCounter(0));
act(() => result.current.increment());
expect(result.current.count).toBe(1);
});
});
it('shows user data after fetch', async () => {
mockSupabase.from('users').select.mockResolvedValue({
data: [{ id: '1', name: 'Alice' }],
error: null,
});
render(<UserList />);
await waitFor(() => {
expect(screen.getByText('Alice')).toBeTruthy();
});
});
it('shows error on fetch failure', async () => {
mockSupabase.from('users').select.mockResolvedValue({
data: null,
error: { message: 'Network error' },
});
render(<UserList />);
await waitFor(() => {
expect(screen.getByText(/error/i)).toBeTruthy();
});
});
// __mocks__/supabase.ts
export const mockSupabase = {
from: jest.fn(() => ({
select: jest.fn().mockResolvedValue({ data: [], error: null }),
insert: jest.fn().mockResolvedValue({ data: [], error: null }),
update: jest.fn().mockResolvedValue({ data: [], error: null }),
delete: jest.fn().mockResolvedValue({ data: [], error: null }),
})),
auth: {
signInWithPassword: jest.fn(),
signUp: jest.fn(),
signOut: jest.fn(),
getSession: jest.fn().mockResolvedValue({ data: { session: null } }),
onAuthStateChange: jest.fn(() => ({ data: { subscription: { unsubscribe: jest.fn() } } })),
},
};
jest.mock('@/lib/supabase', () => ({ supabase: mockSupabase }));
// jest.setup.ts
jest.mock('react-native-reanimated', () =>
require('react-native-reanimated/mock')
);
jest.mock('expo-haptics', () => ({
impactAsync: jest.fn(),
notificationAsync: jest.fn(),
selectionAsync: jest.fn(),
}));
jest.mock('expo-secure-store', () => ({
getItemAsync: jest.fn(),
setItemAsync: jest.fn(),
deleteItemAsync: jest.fn(),
}));
const mockRouter = {
push: jest.fn(),
replace: jest.fn(),
back: jest.fn(),
};
jest.mock('expo-router', () => ({
useRouter: () => mockRouter,
useLocalSearchParams: () => ({}),
}));
npx tsc --noEmit && npx jestAnswer in Korean.
development
Visual hierarchy verification — squint test, information density, primary action clarity, and empty state quality for mobile UI. Auto-load when doing design or layout work.
development
Comprehensive design guide for web and mobile applications. Contains 50+ styles, 97 color palettes, 57 font pairings, 99 UX guidelines, and 25 chart types.
testing
Spacing, layout, and spatial relationships — consistent scale, proximity grouping, optical alignment, and density control for mobile UI. Auto-load when doing design or layout work.
development
React Native Reanimated 4 animation patterns, worklet rules, gesture handler integration, and performance best practices. Auto-load when writing animations, gestures, or motion code.