// ABOUTME: Unit tests for SocialFeedScreen component
// ABOUTME: Tests feed display, reactions, adapt feature, and navigation
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
// Mock navigation
const mockNavigation = {
navigate: jest.fn(),
goBack: jest.fn(),
};
// Mock useFocusEffect - needs to be before imports that use it
jest.mock('@react-navigation/native', () => {
const actualReact = jest.requireActual('react');
return {
useFocusEffect: (callback: () => (() => void) | void) => {
actualReact.useEffect(() => {
return callback();
}, [callback]);
},
useNavigation: () => mockNavigation,
};
});
// Mock AuthContext
jest.mock('../src/contexts/AuthContext', () => ({
useAuth: () => ({
isAuthenticated: true,
}),
}));
// Mock API service
const mockGetSocialFeed = jest.fn();
const mockAddReaction = jest.fn();
const mockRemoveReaction = jest.fn();
jest.mock('../src/services/api', () => ({
socialApi: {
getSocialFeed: (...args: unknown[]) => mockGetSocialFeed(...args),
addReaction: (...args: unknown[]) => mockAddReaction(...args),
removeReaction: (...args: unknown[]) => mockRemoveReaction(...args),
},
}));
import { SocialFeedScreen } from '../src/screens/social/SocialFeedScreen';
import type { FeedItem, SharedInsight, FeedAuthor, ReactionCounts } from '../src/types';
const createMockFeedItem = (overrides: Partial<FeedItem> = {}): FeedItem => {
const insight: SharedInsight = {
id: 'insight-1',
user_id: 'user-2',
visibility: 'friends_only',
insight_type: 'achievement',
sport_type: 'Running',
content: 'Just completed my first marathon training block!',
title: 'Marathon Ready',
training_phase: 'build',
reaction_count: 5,
adapt_count: 2,
created_at: '2024-01-01T00:00:00Z',
updated_at: '2024-01-01T00:00:00Z',
expires_at: null,
source_activity_id: null,
coach_generated: false,
};
const author: FeedAuthor = {
user_id: 'user-2',
display_name: 'Jane Doe',
email: 'jane@example.com',
};
const reactions: ReactionCounts = {
like: 3,
celebrate: 2,
inspire: 0,
support: 0,
total: 5,
};
return {
insight,
author,
reactions,
user_reaction: null,
user_has_adapted: false,
...overrides,
};
};
describe('SocialFeedScreen', () => {
beforeEach(() => {
jest.clearAllMocks();
mockGetSocialFeed.mockResolvedValue({
items: [],
next_cursor: null,
has_more: false,
});
mockAddReaction.mockResolvedValue({
reaction: { id: 'reaction-1', reaction_type: 'like' },
updated_counts: { like: 1, celebrate: 0, inspire: 0, support: 0, total: 1 },
});
mockRemoveReaction.mockResolvedValue(undefined);
});
describe('rendering', () => {
it('should render header with Feed title', async () => {
const { getByText } = render(<SocialFeedScreen />);
await waitFor(() => {
expect(getByText('Feed')).toBeTruthy();
});
});
it('should render empty state when no feed items', async () => {
mockGetSocialFeed.mockResolvedValue({
items: [],
next_cursor: null,
has_more: false,
});
const { getByText } = render(<SocialFeedScreen />);
await waitFor(() => {
expect(getByText('No Insights Yet')).toBeTruthy();
});
});
it('should render header icons for menu and share', async () => {
const { getByText } = render(<SocialFeedScreen />);
await waitFor(() => {
// Header exists with title
expect(getByText('Feed')).toBeTruthy();
});
// Icons are TouchableOpacity components with Feather icons
// We verify the header renders which contains menu and plus-circle icons
});
});
describe('feed items', () => {
it('should render feed item cards', async () => {
const items = [
createMockFeedItem({
insight: {
...createMockFeedItem().insight,
id: 'insight-1',
title: 'Marathon Training Update',
},
}),
createMockFeedItem({
insight: {
...createMockFeedItem().insight,
id: 'insight-2',
title: 'Recovery Tips',
insight_type: 'recovery',
},
author: {
user_id: 'user-3',
display_name: 'Bob Smith',
email: 'bob@example.com',
},
}),
];
mockGetSocialFeed.mockResolvedValue({
items,
next_cursor: null,
has_more: false,
});
const { getByText } = render(<SocialFeedScreen />);
await waitFor(() => {
expect(getByText('Marathon Training Update')).toBeTruthy();
expect(getByText('Recovery Tips')).toBeTruthy();
expect(getByText('Jane Doe')).toBeTruthy();
expect(getByText('Bob Smith')).toBeTruthy();
});
});
it('should display insight content', async () => {
const items = [
createMockFeedItem({
insight: {
...createMockFeedItem().insight,
content: 'Great tempo run today!',
},
}),
];
mockGetSocialFeed.mockResolvedValue({
items,
next_cursor: null,
has_more: false,
});
const { getByText } = render(<SocialFeedScreen />);
await waitFor(() => {
expect(getByText('Great tempo run today!')).toBeTruthy();
});
});
it('should display insight type badge', async () => {
const items = [createMockFeedItem()];
mockGetSocialFeed.mockResolvedValue({
items,
next_cursor: null,
has_more: false,
});
const { getByText } = render(<SocialFeedScreen />);
await waitFor(() => {
expect(getByText('Achievement')).toBeTruthy();
});
});
it('should display sport type when provided', async () => {
const items = [
createMockFeedItem({
insight: {
...createMockFeedItem().insight,
sport_type: 'Cycling',
},
}),
];
mockGetSocialFeed.mockResolvedValue({
items,
next_cursor: null,
has_more: false,
});
const { getByText } = render(<SocialFeedScreen />);
await waitFor(() => {
expect(getByText('Cycling')).toBeTruthy();
});
});
});
describe('reactions', () => {
it('should display reaction counts', async () => {
const items = [createMockFeedItem()];
mockGetSocialFeed.mockResolvedValue({
items,
next_cursor: null,
has_more: false,
});
const { getByText } = render(<SocialFeedScreen />);
await waitFor(() => {
expect(getByText('Marathon Ready')).toBeTruthy();
});
// Verify reaction counts are displayed
expect(getByText('3')).toBeTruthy(); // like count
expect(getByText('2')).toBeTruthy(); // celebrate count
});
});
describe('adapt feature', () => {
it('should show Adapt to My Training button on feed items', async () => {
const items = [createMockFeedItem()];
mockGetSocialFeed.mockResolvedValue({
items,
next_cursor: null,
has_more: false,
});
const { getByText } = render(<SocialFeedScreen />);
await waitFor(() => {
expect(getByText('Adapt to My Training')).toBeTruthy();
});
});
it('should show Adapted indicator when user has adapted', async () => {
const items = [
createMockFeedItem({
user_has_adapted: true,
}),
];
mockGetSocialFeed.mockResolvedValue({
items,
next_cursor: null,
has_more: false,
});
const { getByText } = render(<SocialFeedScreen />);
await waitFor(() => {
expect(getByText('Adapted')).toBeTruthy();
});
});
});
});