diff --git a/src/pages/sidepanel/sections/ProfileSummary.test.tsx b/src/pages/sidepanel/sections/ProfileSummary.test.tsx
new file mode 100644
index 0000000..2a5a8df
--- /dev/null
+++ b/src/pages/sidepanel/sections/ProfileSummary.test.tsx
@@ -0,0 +1,149 @@
+import React from 'react';
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import ProfileSummary from './ProfileSummary';
+import { ThemeProvider } from '@mui/material/styles';
+import { appTheme } from '@root/src/theme';
+
+vi.mock('@root/src/pages/sidepanel/sections/profile/ProfileHeader', () => ({
+ default: ({ lyticsId }) =>
{lyticsId}
,
+}));
+
+vi.mock('@root/src/pages/sidepanel/sections/profile/AudienceMembership', () => ({
+ default: ({ audiences }) => {audiences.join(', ')}
,
+}));
+
+vi.mock('@root/src/pages/sidepanel/sections/profile/Attributes', () => ({
+ default: ({ count }) => Count: {count}
,
+}));
+
+vi.mock('@root/src/pages/sidepanel/sections/profile/BehaviorMetrics', () => ({
+ default: ({ metrics }) => {metrics.length} metrics
,
+}));
+
+vi.mock('@root/src/pages/sidepanel/sections/profile/Interests', () => ({
+ default: ({ interests }) => (
+
+ {interests.map((interest, index) => (
+
+ {interest.label}: {interest.value}%
+
+ ))}
+
+ ),
+}));
+
+vi.mock('@root/src/pages/sidepanel/sections/profile/ProfileMetadata', () => ({
+ default: () => Metadata
,
+}));
+
+describe('ProfileSummary', () => {
+ const renderWithTheme = component => {
+ return render({component});
+ };
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('transforms lytics_content object to interests array correctly', () => {
+ const mockProfile = {
+ data: {
+ user: {
+ _id: 'test-id',
+ _uid: 'test-uid',
+ lytics_content: {
+ Home: 1,
+ Soap: 0.8409454388537114,
+ },
+ },
+ },
+ };
+
+ renderWithTheme();
+
+ const interestsContainer = screen.getByTestId('interests');
+ expect(interestsContainer).toBeInTheDocument();
+
+ expect(screen.getByTestId('interest-0')).toHaveTextContent('Home: 100%');
+ expect(screen.getByTestId('interest-1')).toHaveTextContent('Soap: 84%');
+ });
+
+ it('handles missing lytics_content gracefully', () => {
+ const mockProfile = {
+ data: {
+ user: {
+ _id: 'test-id',
+ _uid: 'test-uid',
+ },
+ },
+ };
+
+ renderWithTheme();
+
+ const interestsContainer = screen.getByTestId('interests');
+ expect(interestsContainer).toBeInTheDocument();
+ expect(interestsContainer.children).toHaveLength(0);
+ });
+
+ it('converts interest values to percentages correctly', () => {
+ const mockProfile = {
+ data: {
+ user: {
+ _id: 'test-id',
+ _uid: 'test-uid',
+ lytics_content: {
+ Technology: 0.95,
+ Sports: 0.6,
+ Music: 0.45,
+ Full: 1.0,
+ Zero: 0,
+ },
+ },
+ },
+ };
+
+ renderWithTheme();
+
+ expect(screen.getByTestId('interest-0')).toHaveTextContent('Technology: 95%');
+ expect(screen.getByTestId('interest-1')).toHaveTextContent('Sports: 60%');
+ expect(screen.getByTestId('interest-2')).toHaveTextContent('Music: 45%');
+ expect(screen.getByTestId('interest-3')).toHaveTextContent('Full: 100%');
+ expect(screen.getByTestId('interest-4')).toHaveTextContent('Zero: 0%');
+ });
+
+ it('handles null or undefined lytics_content', () => {
+ const mockProfile = {
+ data: {
+ user: {
+ _id: 'test-id',
+ _uid: 'test-uid',
+ lytics_content: null,
+ },
+ },
+ };
+
+ renderWithTheme();
+
+ const interestsContainer = screen.getByTestId('interests');
+ expect(interestsContainer.children).toHaveLength(0);
+ });
+
+ it('handles empty lytics_content object', () => {
+ const mockProfile = {
+ data: {
+ user: {
+ _id: 'test-id',
+ _uid: 'test-uid',
+ lytics_content: {},
+ },
+ },
+ };
+
+ renderWithTheme();
+
+ const interestsContainer = screen.getByTestId('interests');
+ expect(interestsContainer.children).toHaveLength(0);
+ });
+});
diff --git a/src/pages/sidepanel/sections/ProfileSummary.tsx b/src/pages/sidepanel/sections/ProfileSummary.tsx
index 04c220f..76a88f6 100644
--- a/src/pages/sidepanel/sections/ProfileSummary.tsx
+++ b/src/pages/sidepanel/sections/ProfileSummary.tsx
@@ -92,6 +92,16 @@ const ProfileSummary: React.FC = ({ profile }) => {
return attributes.length > 0 ? attributes[0] : 'Unknown';
}, [profile?.data?.user]);
+ const interestsArray = useMemo(() => {
+ const lyticsContent = profile?.data?.user?.lytics_content;
+ if (!lyticsContent || typeof lyticsContent !== 'object') return [];
+
+ return Object.entries(lyticsContent).map(([label, value]) => ({
+ label,
+ value: Math.round((typeof value === 'number' ? value : 0) * 100),
+ }));
+ }, [profile?.data?.user?.lytics_content]);
+
return (
= ({ profile }) => {
/>
)}
-
+
diff --git a/src/pages/sidepanel/sections/profile/Interests.test.tsx b/src/pages/sidepanel/sections/profile/Interests.test.tsx
new file mode 100644
index 0000000..d40aaf5
--- /dev/null
+++ b/src/pages/sidepanel/sections/profile/Interests.test.tsx
@@ -0,0 +1,63 @@
+import React from 'react';
+import { describe, it, expect } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { Interests } from './Interests';
+import { ThemeProvider } from '@mui/material/styles';
+import { appTheme } from '@root/src/theme';
+
+describe('Interests', () => {
+ const renderWithTheme = component => {
+ return render({component});
+ };
+
+ it('renders interests with progress bars when data is available', () => {
+ const mockInterests = [
+ { label: 'Home', value: 100 },
+ { label: 'Soap', value: 84 },
+ ];
+
+ renderWithTheme();
+
+ expect(screen.getByText('Interests')).toBeInTheDocument();
+ expect(screen.getByText('Home')).toBeInTheDocument();
+ expect(screen.getByText('Soap')).toBeInTheDocument();
+
+ const progressBars = screen.getAllByRole('progressbar');
+ expect(progressBars).toHaveLength(2);
+ expect(progressBars[0]).toHaveAttribute('aria-label', 'Home: 100%');
+ expect(progressBars[1]).toHaveAttribute('aria-label', 'Soap: 84%');
+ });
+
+ it('displays empty state when hasData is false', () => {
+ renderWithTheme();
+
+ expect(screen.getByText('Interests')).toBeInTheDocument();
+ expect(screen.getByText(/Interests are not currently shared for this account/)).toBeInTheDocument();
+ expect(screen.getByText('Learn more')).toBeInTheDocument();
+ });
+
+ it('displays empty state when interests array is empty', () => {
+ renderWithTheme();
+
+ expect(screen.getByText('Interests')).toBeInTheDocument();
+ expect(screen.getByText(/Interests are not currently shared for this account/)).toBeInTheDocument();
+ });
+
+ it('renders multiple interests correctly', () => {
+ const mockInterests = [
+ { label: 'Technology', value: 95 },
+ { label: 'Sports', value: 60 },
+ { label: 'Music', value: 45 },
+ ];
+
+ renderWithTheme();
+
+ expect(screen.getByText('Technology')).toBeInTheDocument();
+ expect(screen.getByText('Sports')).toBeInTheDocument();
+ expect(screen.getByText('Music')).toBeInTheDocument();
+
+ const progressBars = screen.getAllByRole('progressbar');
+ expect(progressBars).toHaveLength(3);
+ });
+});
diff --git a/src/pages/sidepanel/sections/profile/Interests.tsx b/src/pages/sidepanel/sections/profile/Interests.tsx
index b120c73..fe61a09 100644
--- a/src/pages/sidepanel/sections/profile/Interests.tsx
+++ b/src/pages/sidepanel/sections/profile/Interests.tsx
@@ -1,13 +1,18 @@
import React from 'react';
-import { Box, Stack, Typography, Link } from '@mui/material';
+import { Box, Stack, Typography, Link, LinearProgress } from '@mui/material';
import { styled } from '@mui/material/styles';
import { Lock } from '@mui/icons-material';
import { appColors } from '@root/src/theme/palette';
import { appContent } from '@root/src/shared/content/appContent';
+interface Interest {
+ label: string;
+ value: number;
+}
+
interface InterestsProps {
hasData: boolean;
- interests?: string[];
+ interests?: Interest[];
textContent?: typeof appContent.interests;
}
@@ -68,11 +73,33 @@ const StyledLink = styled(Link)(() => ({
},
}));
-const ContentText = styled(Typography)(() => ({
+const InterestsContainer = styled(Stack)(({ theme }) => ({
+ display: 'flex',
+ flexDirection: 'column',
+ gap: theme.spacing(1),
+}));
+
+const InterestRow = styled(Stack)(({ theme }) => ({
+ display: 'flex',
+ flexDirection: 'column',
+ gap: theme.spacing(0.5),
+}));
+
+const InterestLabel = styled(Typography)(() => ({
fontSize: appColors.common.fontSize.baseSmall,
- color: appColors.neutral[600],
- lineHeight: appColors.common.lineHeight.tight,
fontWeight: appColors.common.fontWeight.medium,
+ color: appColors.neutral[900],
+}));
+
+const StyledLinearProgress = styled(LinearProgress)(({ theme }) => ({
+ width: '100%',
+ height: '0.5rem', // 8px
+ borderRadius: theme.spacing(0.5),
+ backgroundColor: appColors.common.colors.accentLight,
+ '& .MuiLinearProgress-bar': {
+ backgroundColor: appColors.common.colors.accent,
+ borderRadius: theme.spacing(0.5),
+ },
}));
export const Interests = ({
@@ -85,7 +112,18 @@ export const Interests = ({
{textContent.title}
{hasData && interests.length > 0 ? (
- {interests.join(', ')}
+
+ {interests.map((interest, index) => (
+
+ {interest.label}
+
+
+ ))}
+
) : (