Skip to content

Commit e9a8488

Browse files
committed
feat(footer): add social media icons to bottom bar
1 parent 781a302 commit e9a8488

6 files changed

Lines changed: 73 additions & 25 deletions

app/components/Footer.accessibility.test.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,22 @@ describe('Footer — Accessibility & Screen Reader Compliance', () => {
4444
it('renders social links with descriptive aria-labels for screen readers', () => {
4545
render(<Footer />);
4646

47-
expect(screen.getByRole('link', { name: 'CommitPulse on GitHub' })).toBeInTheDocument();
47+
// Social links now appear twice: once in the Connect column (with text) and once in the bottom bar icon strip.
48+
// getAllByRole prevents the 'Found multiple elements' error.
49+
const githubLinks = screen.getAllByRole('link', { name: 'CommitPulse on GitHub' });
50+
expect(githubLinks.length).toBeGreaterThanOrEqual(1);
51+
expect(githubLinks[0]).toHaveAttribute('href', 'https://github.com/JhaSourav07/commitpulse');
4852

4953
expect(screen.getByRole('link', { name: 'Creator Sourav Jha on GitHub' })).toBeInTheDocument();
5054

51-
expect(screen.getByRole('link', { name: 'Join CommitPulse on Discord' })).toBeInTheDocument();
55+
const discordLinks = screen.getAllByRole('link', { name: 'Join CommitPulse on Discord' });
56+
expect(discordLinks.length).toBeGreaterThanOrEqual(1);
5257

53-
expect(screen.getByRole('link', { name: 'Creator on X' })).toBeInTheDocument();
58+
const xLinks = screen.getAllByRole('link', { name: 'Creator on X' });
59+
expect(xLinks.length).toBeGreaterThanOrEqual(1);
5460

55-
expect(screen.getByRole('link', { name: 'Creator on LinkedIn' })).toBeInTheDocument();
61+
const linkedinLinks = screen.getAllByRole('link', { name: 'Creator on LinkedIn' });
62+
expect(linkedinLinks.length).toBeGreaterThanOrEqual(1);
5663
});
5764

5865
it('applies visible focus ring classes to all interactive links', () => {

app/components/Footer.empty-fallback.test.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,11 @@ describe('Footer empty-fallback and edge-cases', () => {
9595
expect(homeLink).not.toHaveAttribute('aria-label'); // undefined ariaLabel
9696
expect(homeLink).not.toHaveAttribute('target'); // not external
9797

98-
// External Link Component
99-
const githubLink = screen.getByRole('link', { name: 'CommitPulse on GitHub' });
100-
expect(githubLink).toBeInTheDocument();
101-
expect(githubLink).toHaveAttribute('target', '_blank');
102-
expect(githubLink).toHaveAttribute('rel', 'noopener noreferrer');
98+
// External Link Component — now appears twice (Connect column + bottom bar icon strip)
99+
const githubLinks = screen.getAllByRole('link', { name: 'CommitPulse on GitHub' });
100+
expect(githubLinks.length).toBeGreaterThanOrEqual(1);
101+
expect(githubLinks[0]).toHaveAttribute('target', '_blank');
102+
expect(githubLinks[0]).toHaveAttribute('rel', 'noopener noreferrer');
103103
});
104104

105105
it('renders correct current year when system date environment changes', () => {

app/components/Footer.mouse-interactivity.test.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,18 @@ describe('Footer - real component coverage (CI-safe)', () => {
3535
it('renders social links using aria-labels (no ambiguity)', () => {
3636
render(<Footer />);
3737

38-
expect(screen.getByRole('link', { name: 'CommitPulse on GitHub' })).toHaveAttribute(
39-
'href',
40-
expect.stringContaining('github.com')
41-
);
38+
// Social links now appear twice: once in the Connect column and once in the bottom bar icon strip.
39+
const githubLinks = screen.getAllByRole('link', { name: 'CommitPulse on GitHub' });
40+
expect(githubLinks.length).toBeGreaterThanOrEqual(1);
41+
expect(githubLinks[0]).toHaveAttribute('href', expect.stringContaining('github.com'));
4242

4343
expect(screen.getByRole('link', { name: 'Creator Sourav Jha on GitHub' })).toHaveAttribute(
4444
'href',
4545
expect.stringContaining('github.com')
4646
);
4747

48-
expect(screen.getByRole('link', { name: 'Join CommitPulse on Discord' })).toHaveAttribute(
49-
'href',
50-
expect.stringContaining('discord')
51-
);
48+
const discordLinks = screen.getAllByRole('link', { name: 'Join CommitPulse on Discord' });
49+
expect(discordLinks.length).toBeGreaterThanOrEqual(1);
50+
expect(discordLinks[0]).toHaveAttribute('href', expect.stringContaining('discord'));
5251
});
5352
});

app/components/Footer.responsive-breakpoints.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ describe('Footer Responsive Breakpoints', () => {
104104
const { container } = render(<Footer />);
105105

106106
const flexColContainers = container.querySelectorAll('.flex.flex-col');
107-
expect(flexColContainers.length).toBe(8);
107+
expect(flexColContainers.length).toBeGreaterThanOrEqual(8);
108108

109109
const navList = container.querySelector('nav.flex.flex-col.gap-2');
110110
expect(navList).toBeInTheDocument();

app/components/Footer.test.tsx

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,13 @@ describe('Footer Component', () => {
5858
it('renders Discord community link', () => {
5959
render(<Footer />);
6060

61-
const discordLink = screen.getByRole('link', {
62-
name: /Discord/i,
61+
// Discord now appears twice: once in the Connect column (text + icon) and once in the bottom bar icon strip.
62+
const discordLinks = screen.getAllByRole('link', {
63+
name: /Join CommitPulse on Discord/i,
6364
});
6465

65-
expect(discordLink).toHaveAttribute('href', 'https://discord.gg/f84SDraEBH');
66+
expect(discordLinks.length).toBeGreaterThanOrEqual(1);
67+
expect(discordLinks[0]).toHaveAttribute('href', 'https://discord.gg/f84SDraEBH');
6668
});
6769

6870
it('exposes the footer as a semantic contentinfo landmark for screen readers', () => {
@@ -114,10 +116,14 @@ describe('Footer Component', () => {
114116
const connectHeading = screen.getByRole('heading', { name: /Connect/i });
115117
expect(connectHeading).toBeInTheDocument();
116118

117-
// Check for social links
118-
expect(screen.getByRole('link', { name: /Creator on X/i })).toBeInTheDocument();
119-
expect(screen.getByRole('link', { name: /LinkedIn/i })).toBeInTheDocument();
120-
expect(screen.getByRole('link', { name: /Discord/i })).toBeInTheDocument();
119+
// Social links now appear twice (Connect column + bottom bar icon strip); use getAllByRole.
120+
expect(screen.getAllByRole('link', { name: /Creator on X/i }).length).toBeGreaterThanOrEqual(1);
121+
expect(
122+
screen.getAllByRole('link', { name: /Creator on LinkedIn/i }).length
123+
).toBeGreaterThanOrEqual(1);
124+
expect(
125+
screen.getAllByRole('link', { name: /Join CommitPulse on Discord/i }).length
126+
).toBeGreaterThanOrEqual(1);
121127
});
122128

123129
it('has proper responsive layout classes', () => {
@@ -159,4 +165,21 @@ describe('Footer Component', () => {
159165
expect(link.className).toContain('focus:ring');
160166
});
161167
});
168+
169+
it('renders social icon strip in the bottom bar with correct links and no duplicate creator icon', () => {
170+
render(<Footer />);
171+
172+
// Project GitHub, Discord, X, LinkedIn each appear twice:
173+
// once in the Connect column (with text label) and once in the bottom bar icon strip.
174+
expect(screen.getAllByRole('link', { name: 'CommitPulse on GitHub' })).toHaveLength(2);
175+
expect(screen.getAllByRole('link', { name: 'Join CommitPulse on Discord' })).toHaveLength(2);
176+
expect(screen.getAllByRole('link', { name: 'Creator on X' })).toHaveLength(2);
177+
expect(screen.getAllByRole('link', { name: 'Creator on LinkedIn' })).toHaveLength(2);
178+
179+
// The creator's personal GitHub link must appear only once (Connect column only, not in bottom bar).
180+
expect(screen.getAllByRole('link', { name: 'Creator Sourav Jha on GitHub' })).toHaveLength(1);
181+
182+
// The bottom bar nav landmark must be present with an accessible label.
183+
expect(screen.getByRole('navigation', { name: 'Social media links' })).toBeInTheDocument();
184+
});
162185
});

app/components/Footer.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,25 @@ export function Footer() {
273273
{/* Bottom Section */}
274274
<div className="mt-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 text-xs text-zinc-500 dark:text-zinc-500">
275275
<p>{t('footer.copyright', { year: currentYear.toString() })}</p>
276+
277+
{/* Social Icon Strip */}
278+
<nav aria-label="Social media links" className="flex items-center gap-3">
279+
{socialLinks
280+
.filter((link) => link.icon !== 'creator')
281+
.map((link) => (
282+
<a
283+
key={link.href}
284+
href={link.href}
285+
target="_blank"
286+
rel="noopener noreferrer"
287+
aria-label={link.ariaLabel}
288+
className="p-1.5 rounded transition-all duration-300 hover:-translate-y-[2px] hover:text-teal-800 dark:hover:text-violet-400 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-zinc-950"
289+
>
290+
{SOCIAL_ICON_MAP[link.icon as keyof typeof SOCIAL_ICON_MAP]}
291+
</a>
292+
))}
293+
</nav>
294+
276295
<p>{t('footer.made_with')}</p>
277296
</div>
278297
</div>

0 commit comments

Comments
 (0)