Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions app/components/Footer.accessibility.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,22 @@ describe('Footer — Accessibility & Screen Reader Compliance', () => {
it('renders social links with descriptive aria-labels for screen readers', () => {
render(<Footer />);

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

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

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

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

expect(screen.getByRole('link', { name: 'Creator on LinkedIn' })).toBeInTheDocument();
const linkedinLinks = screen.getAllByRole('link', { name: 'Creator on LinkedIn' });
expect(linkedinLinks.length).toBeGreaterThanOrEqual(1);
});

it('applies visible focus ring classes to all interactive links', () => {
Expand Down
10 changes: 5 additions & 5 deletions app/components/Footer.empty-fallback.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,11 @@ describe('Footer empty-fallback and edge-cases', () => {
expect(homeLink).not.toHaveAttribute('aria-label'); // undefined ariaLabel
expect(homeLink).not.toHaveAttribute('target'); // not external

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

it('renders correct current year when system date environment changes', () => {
Expand Down
15 changes: 7 additions & 8 deletions app/components/Footer.mouse-interactivity.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,18 @@ describe('Footer - real component coverage (CI-safe)', () => {
it('renders social links using aria-labels (no ambiguity)', () => {
render(<Footer />);

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

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

expect(screen.getByRole('link', { name: 'Join CommitPulse on Discord' })).toHaveAttribute(
'href',
expect.stringContaining('discord')
);
const discordLinks = screen.getAllByRole('link', { name: 'Join CommitPulse on Discord' });
expect(discordLinks.length).toBeGreaterThanOrEqual(1);
expect(discordLinks[0]).toHaveAttribute('href', expect.stringContaining('discord'));
});
});
2 changes: 1 addition & 1 deletion app/components/Footer.responsive-breakpoints.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ describe('Footer Responsive Breakpoints', () => {
const { container } = render(<Footer />);

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

const navList = container.querySelector('nav.flex.flex-col.gap-2');
expect(navList).toBeInTheDocument();
Expand Down
37 changes: 30 additions & 7 deletions app/components/Footer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,13 @@ describe('Footer Component', () => {
it('renders Discord community link', () => {
render(<Footer />);

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

expect(discordLink).toHaveAttribute('href', 'https://discord.gg/f84SDraEBH');
expect(discordLinks.length).toBeGreaterThanOrEqual(1);
expect(discordLinks[0]).toHaveAttribute('href', 'https://discord.gg/f84SDraEBH');
});

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

// Check for social links
expect(screen.getByRole('link', { name: /Creator on X/i })).toBeInTheDocument();
expect(screen.getByRole('link', { name: /LinkedIn/i })).toBeInTheDocument();
expect(screen.getByRole('link', { name: /Discord/i })).toBeInTheDocument();
// Social links now appear twice (Connect column + bottom bar icon strip); use getAllByRole.
expect(screen.getAllByRole('link', { name: /Creator on X/i }).length).toBeGreaterThanOrEqual(1);
expect(
screen.getAllByRole('link', { name: /Creator on LinkedIn/i }).length
).toBeGreaterThanOrEqual(1);
expect(
screen.getAllByRole('link', { name: /Join CommitPulse on Discord/i }).length
).toBeGreaterThanOrEqual(1);
});

it('has proper responsive layout classes', () => {
Expand Down Expand Up @@ -159,4 +165,21 @@ describe('Footer Component', () => {
expect(link.className).toContain('focus:ring');
});
});

it('renders social icon strip in the bottom bar with correct links and no duplicate creator icon', () => {
render(<Footer />);

// Project GitHub, Discord, X, LinkedIn each appear twice:
// once in the Connect column (with text label) and once in the bottom bar icon strip.
expect(screen.getAllByRole('link', { name: 'CommitPulse on GitHub' })).toHaveLength(2);
expect(screen.getAllByRole('link', { name: 'Join CommitPulse on Discord' })).toHaveLength(2);
expect(screen.getAllByRole('link', { name: 'Creator on X' })).toHaveLength(2);
expect(screen.getAllByRole('link', { name: 'Creator on LinkedIn' })).toHaveLength(2);

// The creator's personal GitHub link must appear only once (Connect column only, not in bottom bar).
expect(screen.getAllByRole('link', { name: 'Creator Sourav Jha on GitHub' })).toHaveLength(1);

// The bottom bar nav landmark must be present with an accessible label.
expect(screen.getByRole('navigation', { name: 'Social media links' })).toBeInTheDocument();
});
});
19 changes: 19 additions & 0 deletions app/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,25 @@ export function Footer() {
{/* Bottom Section */}
<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">
<p>{t('footer.copyright', { year: currentYear.toString() })}</p>

{/* Social Icon Strip */}
<nav aria-label="Social media links" className="flex items-center gap-3">
{socialLinks
.filter((link) => link.icon !== 'creator')
.map((link) => (
<a
key={link.href}
href={link.href}
target="_blank"
rel="noopener noreferrer"
aria-label={link.ariaLabel}
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"
>
{SOCIAL_ICON_MAP[link.icon as keyof typeof SOCIAL_ICON_MAP]}
</a>
))}
</nav>

<p>{t('footer.made_with')}</p>
</div>
</div>
Expand Down
Loading