Skip to content

Commit afad95e

Browse files
authored
added missing Interests in profile section (#131)
1 parent 981f59b commit afad95e

File tree

4 files changed

+267
-7
lines changed

4 files changed

+267
-7
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import React from 'react';
2+
import { describe, it, expect, vi, beforeEach } from 'vitest';
3+
import { render, screen } from '@testing-library/react';
4+
import '@testing-library/jest-dom';
5+
import ProfileSummary from './ProfileSummary';
6+
import { ThemeProvider } from '@mui/material/styles';
7+
import { appTheme } from '@root/src/theme';
8+
9+
vi.mock('@root/src/pages/sidepanel/sections/profile/ProfileHeader', () => ({
10+
default: ({ lyticsId }) => <div data-testid="profile-header">{lyticsId}</div>,
11+
}));
12+
13+
vi.mock('@root/src/pages/sidepanel/sections/profile/AudienceMembership', () => ({
14+
default: ({ audiences }) => <div data-testid="audience-membership">{audiences.join(', ')}</div>,
15+
}));
16+
17+
vi.mock('@root/src/pages/sidepanel/sections/profile/Attributes', () => ({
18+
default: ({ count }) => <div data-testid="attributes">Count: {count}</div>,
19+
}));
20+
21+
vi.mock('@root/src/pages/sidepanel/sections/profile/BehaviorMetrics', () => ({
22+
default: ({ metrics }) => <div data-testid="behavior-metrics">{metrics.length} metrics</div>,
23+
}));
24+
25+
vi.mock('@root/src/pages/sidepanel/sections/profile/Interests', () => ({
26+
default: ({ interests }) => (
27+
<div data-testid="interests">
28+
{interests.map((interest, index) => (
29+
<div key={index} data-testid={`interest-${index}`}>
30+
{interest.label}: {interest.value}%
31+
</div>
32+
))}
33+
</div>
34+
),
35+
}));
36+
37+
vi.mock('@root/src/pages/sidepanel/sections/profile/ProfileMetadata', () => ({
38+
default: () => <div data-testid="profile-metadata">Metadata</div>,
39+
}));
40+
41+
describe('ProfileSummary', () => {
42+
const renderWithTheme = component => {
43+
return render(<ThemeProvider theme={appTheme}>{component}</ThemeProvider>);
44+
};
45+
46+
beforeEach(() => {
47+
vi.clearAllMocks();
48+
});
49+
50+
it('transforms lytics_content object to interests array correctly', () => {
51+
const mockProfile = {
52+
data: {
53+
user: {
54+
_id: 'test-id',
55+
_uid: 'test-uid',
56+
lytics_content: {
57+
Home: 1,
58+
Soap: 0.8409454388537114,
59+
},
60+
},
61+
},
62+
};
63+
64+
renderWithTheme(<ProfileSummary profile={mockProfile} />);
65+
66+
const interestsContainer = screen.getByTestId('interests');
67+
expect(interestsContainer).toBeInTheDocument();
68+
69+
expect(screen.getByTestId('interest-0')).toHaveTextContent('Home: 100%');
70+
expect(screen.getByTestId('interest-1')).toHaveTextContent('Soap: 84%');
71+
});
72+
73+
it('handles missing lytics_content gracefully', () => {
74+
const mockProfile = {
75+
data: {
76+
user: {
77+
_id: 'test-id',
78+
_uid: 'test-uid',
79+
},
80+
},
81+
};
82+
83+
renderWithTheme(<ProfileSummary profile={mockProfile} />);
84+
85+
const interestsContainer = screen.getByTestId('interests');
86+
expect(interestsContainer).toBeInTheDocument();
87+
expect(interestsContainer.children).toHaveLength(0);
88+
});
89+
90+
it('converts interest values to percentages correctly', () => {
91+
const mockProfile = {
92+
data: {
93+
user: {
94+
_id: 'test-id',
95+
_uid: 'test-uid',
96+
lytics_content: {
97+
Technology: 0.95,
98+
Sports: 0.6,
99+
Music: 0.45,
100+
Full: 1.0,
101+
Zero: 0,
102+
},
103+
},
104+
},
105+
};
106+
107+
renderWithTheme(<ProfileSummary profile={mockProfile} />);
108+
109+
expect(screen.getByTestId('interest-0')).toHaveTextContent('Technology: 95%');
110+
expect(screen.getByTestId('interest-1')).toHaveTextContent('Sports: 60%');
111+
expect(screen.getByTestId('interest-2')).toHaveTextContent('Music: 45%');
112+
expect(screen.getByTestId('interest-3')).toHaveTextContent('Full: 100%');
113+
expect(screen.getByTestId('interest-4')).toHaveTextContent('Zero: 0%');
114+
});
115+
116+
it('handles null or undefined lytics_content', () => {
117+
const mockProfile = {
118+
data: {
119+
user: {
120+
_id: 'test-id',
121+
_uid: 'test-uid',
122+
lytics_content: null,
123+
},
124+
},
125+
};
126+
127+
renderWithTheme(<ProfileSummary profile={mockProfile} />);
128+
129+
const interestsContainer = screen.getByTestId('interests');
130+
expect(interestsContainer.children).toHaveLength(0);
131+
});
132+
133+
it('handles empty lytics_content object', () => {
134+
const mockProfile = {
135+
data: {
136+
user: {
137+
_id: 'test-id',
138+
_uid: 'test-uid',
139+
lytics_content: {},
140+
},
141+
},
142+
};
143+
144+
renderWithTheme(<ProfileSummary profile={mockProfile} />);
145+
146+
const interestsContainer = screen.getByTestId('interests');
147+
expect(interestsContainer.children).toHaveLength(0);
148+
});
149+
});

src/pages/sidepanel/sections/ProfileSummary.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,16 @@ const ProfileSummary: React.FC<ProfileSummaryTabProps> = ({ profile }) => {
9292
return attributes.length > 0 ? attributes[0] : 'Unknown';
9393
}, [profile?.data?.user]);
9494

95+
const interestsArray = useMemo(() => {
96+
const lyticsContent = profile?.data?.user?.lytics_content;
97+
if (!lyticsContent || typeof lyticsContent !== 'object') return [];
98+
99+
return Object.entries(lyticsContent).map(([label, value]) => ({
100+
label,
101+
value: Math.round((typeof value === 'number' ? value : 0) * 100),
102+
}));
103+
}, [profile?.data?.user?.lytics_content]);
104+
95105
return (
96106
<Box
97107
sx={{
@@ -126,7 +136,7 @@ const ProfileSummary: React.FC<ProfileSummaryTabProps> = ({ profile }) => {
126136
/>
127137
)}
128138

129-
<Interests hasData={hasContent} interests={profile?.data?.user?.lytics_content || []} />
139+
<Interests hasData={hasContent} interests={interestsArray} />
130140

131141
<ProfileMetadata lastUpdated={lastUpdatedText} lastAttribute={lastAttributeText} />
132142
</Box>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React from 'react';
2+
import { describe, it, expect } from 'vitest';
3+
import { render, screen } from '@testing-library/react';
4+
import '@testing-library/jest-dom';
5+
import { Interests } from './Interests';
6+
import { ThemeProvider } from '@mui/material/styles';
7+
import { appTheme } from '@root/src/theme';
8+
9+
describe('Interests', () => {
10+
const renderWithTheme = component => {
11+
return render(<ThemeProvider theme={appTheme}>{component}</ThemeProvider>);
12+
};
13+
14+
it('renders interests with progress bars when data is available', () => {
15+
const mockInterests = [
16+
{ label: 'Home', value: 100 },
17+
{ label: 'Soap', value: 84 },
18+
];
19+
20+
renderWithTheme(<Interests hasData={true} interests={mockInterests} />);
21+
22+
expect(screen.getByText('Interests')).toBeInTheDocument();
23+
expect(screen.getByText('Home')).toBeInTheDocument();
24+
expect(screen.getByText('Soap')).toBeInTheDocument();
25+
26+
const progressBars = screen.getAllByRole('progressbar');
27+
expect(progressBars).toHaveLength(2);
28+
expect(progressBars[0]).toHaveAttribute('aria-label', 'Home: 100%');
29+
expect(progressBars[1]).toHaveAttribute('aria-label', 'Soap: 84%');
30+
});
31+
32+
it('displays empty state when hasData is false', () => {
33+
renderWithTheme(<Interests hasData={false} interests={[]} />);
34+
35+
expect(screen.getByText('Interests')).toBeInTheDocument();
36+
expect(screen.getByText(/Interests are not currently shared for this account/)).toBeInTheDocument();
37+
expect(screen.getByText('Learn more')).toBeInTheDocument();
38+
});
39+
40+
it('displays empty state when interests array is empty', () => {
41+
renderWithTheme(<Interests hasData={true} interests={[]} />);
42+
43+
expect(screen.getByText('Interests')).toBeInTheDocument();
44+
expect(screen.getByText(/Interests are not currently shared for this account/)).toBeInTheDocument();
45+
});
46+
47+
it('renders multiple interests correctly', () => {
48+
const mockInterests = [
49+
{ label: 'Technology', value: 95 },
50+
{ label: 'Sports', value: 60 },
51+
{ label: 'Music', value: 45 },
52+
];
53+
54+
renderWithTheme(<Interests hasData={true} interests={mockInterests} />);
55+
56+
expect(screen.getByText('Technology')).toBeInTheDocument();
57+
expect(screen.getByText('Sports')).toBeInTheDocument();
58+
expect(screen.getByText('Music')).toBeInTheDocument();
59+
60+
const progressBars = screen.getAllByRole('progressbar');
61+
expect(progressBars).toHaveLength(3);
62+
});
63+
});

src/pages/sidepanel/sections/profile/Interests.tsx

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import React from 'react';
2-
import { Box, Stack, Typography, Link } from '@mui/material';
2+
import { Box, Stack, Typography, Link, LinearProgress } from '@mui/material';
33
import { styled } from '@mui/material/styles';
44
import { Lock } from '@mui/icons-material';
55
import { appColors } from '@root/src/theme/palette';
66
import { appContent } from '@root/src/shared/content/appContent';
77

8+
interface Interest {
9+
label: string;
10+
value: number;
11+
}
12+
813
interface InterestsProps {
914
hasData: boolean;
10-
interests?: string[];
15+
interests?: Interest[];
1116
textContent?: typeof appContent.interests;
1217
}
1318

@@ -68,11 +73,33 @@ const StyledLink = styled(Link)(() => ({
6873
},
6974
}));
7075

71-
const ContentText = styled(Typography)(() => ({
76+
const InterestsContainer = styled(Stack)(({ theme }) => ({
77+
display: 'flex',
78+
flexDirection: 'column',
79+
gap: theme.spacing(1),
80+
}));
81+
82+
const InterestRow = styled(Stack)(({ theme }) => ({
83+
display: 'flex',
84+
flexDirection: 'column',
85+
gap: theme.spacing(0.5),
86+
}));
87+
88+
const InterestLabel = styled(Typography)(() => ({
7289
fontSize: appColors.common.fontSize.baseSmall,
73-
color: appColors.neutral[600],
74-
lineHeight: appColors.common.lineHeight.tight,
7590
fontWeight: appColors.common.fontWeight.medium,
91+
color: appColors.neutral[900],
92+
}));
93+
94+
const StyledLinearProgress = styled(LinearProgress)(({ theme }) => ({
95+
width: '100%',
96+
height: '0.5rem', // 8px
97+
borderRadius: theme.spacing(0.5),
98+
backgroundColor: appColors.common.colors.accentLight,
99+
'& .MuiLinearProgress-bar': {
100+
backgroundColor: appColors.common.colors.accent,
101+
borderRadius: theme.spacing(0.5),
102+
},
76103
}));
77104

78105
export const Interests = ({
@@ -85,7 +112,18 @@ export const Interests = ({
85112
<TitleText>{textContent.title}</TitleText>
86113

87114
{hasData && interests.length > 0 ? (
88-
<ContentText>{interests.join(', ')}</ContentText>
115+
<InterestsContainer>
116+
{interests.map((interest, index) => (
117+
<InterestRow key={index}>
118+
<InterestLabel>{interest.label}</InterestLabel>
119+
<StyledLinearProgress
120+
variant="determinate"
121+
value={interest.value}
122+
aria-label={`${interest.label}: ${interest.value}%`}
123+
/>
124+
</InterestRow>
125+
))}
126+
</InterestsContainer>
89127
) : (
90128
<EmptyStateContainer>
91129
<LockIcon aria-hidden="true" />

0 commit comments

Comments
 (0)