Skip to content

Commit 1a1522d

Browse files
committed
test: improve footer accessibility and responsive coverage
1 parent f2e989d commit 1a1522d

5 files changed

Lines changed: 197 additions & 100 deletions

File tree

app/components/Footer.test.tsx

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,82 @@
1-
import { describe, it, expect } from 'vitest';
1+
import '@testing-library/jest-dom/vitest';
22
import { render, screen } from '@testing-library/react';
3+
import { describe, expect, it } from 'vitest';
34
import { Footer } from './Footer';
45

56
describe('Footer Component', () => {
67
it('renders community text', () => {
78
render(<Footer />);
89

9-
const text = screen.getByText(/Designed for the elite builder community/i);
10-
11-
expect(text).toBeTruthy();
10+
expect(screen.getByText(/Designed for the elite builder community/i)).toBeInTheDocument();
1211
});
1312

14-
it('renders Documentation link', () => {
13+
it('renders Documentation link with the correct destination', () => {
1514
render(<Footer />);
1615

17-
const docLink = screen.getByText(/Documentation/i);
18-
19-
expect(docLink).toBeTruthy();
16+
const documentationLink = screen.getByRole('link', {
17+
name: /Documentation/i,
18+
});
2019

21-
expect(docLink.closest('a')?.getAttribute('href')).toBe(
20+
expect(documentationLink).toHaveAttribute(
21+
'href',
2222
'https://github.com/JhaSourav07/commitpulse/blob/main/README.md'
2323
);
2424
});
2525

26-
it('opens documentation in new tab', () => {
26+
it('opens Documentation link in a new tab securely', () => {
2727
render(<Footer />);
2828

29-
const docLink = screen.getByText(/Documentation/i);
29+
const documentationLink = screen.getByRole('link', {
30+
name: /Documentation/i,
31+
});
3032

31-
expect(docLink.closest('a')?.getAttribute('target')).toBe('_blank');
33+
expect(documentationLink).toHaveAttribute('target', '_blank');
34+
expect(documentationLink).toHaveAttribute('rel', expect.stringContaining('noopener'));
35+
expect(documentationLink).toHaveAttribute('rel', expect.stringContaining('noreferrer'));
3236
});
3337

3438
it('renders Contributors link', () => {
3539
render(<Footer />);
3640

37-
const contributorsLink = screen.getByText(/Contributors/i);
38-
39-
expect(contributorsLink).toBeTruthy();
41+
expect(
42+
screen.getByRole('link', {
43+
name: /Contributors/i,
44+
})
45+
).toBeInTheDocument();
4046
});
41-
// Verify accessibility by looking for the explicit landmark role screen readers look for
42-
it('supports screen reader operations by using semantic HTML landmark structures', () => {
47+
48+
it('exposes the footer as a semantic contentinfo landmark for screen readers', () => {
4349
render(<Footer />);
4450

45-
// Footers are exposed to assistive tech via the 'contentinfo' role
46-
const footerElement = screen.getByRole('contentinfo');
47-
expect(footerElement).not.toBeNull();
48-
expect(footerElement.tagName.toLowerCase()).toBe('footer');
51+
// A semantic <footer> is exposed to assistive technology as the contentinfo landmark.
52+
const footer = screen.getByRole('contentinfo');
53+
54+
expect(footer).toBeInTheDocument();
55+
expect(footer.tagName).toBe('FOOTER');
4956
});
5057

51-
// Verify layout adaptation by making sure responsive structural classes are applied
52-
it('adapts layouts correctly across mobile and desktop viewports', () => {
53-
const { container } = render(<Footer />);
54-
55-
const footerElement = container.querySelector('footer');
56-
expect(footerElement).not.toBeNull();
57-
58-
// Directly grab the text content or class attributes to check for responsive utility markers safely
59-
const hasResponsiveClasses = footerElement?.className
60-
.split(' ')
61-
.some(
62-
(cls) =>
63-
cls.includes('md:') ||
64-
cls.includes('sm:') ||
65-
cls.includes('lg:') ||
66-
cls.includes('flex') ||
67-
cls.includes('grid')
68-
);
69-
70-
expect(hasResponsiveClasses).toBe(true);
58+
it('includes responsive layout classes for mobile and desktop breakpoints', () => {
59+
render(<Footer />);
60+
61+
const footer = screen.getByRole('contentinfo');
62+
63+
// JSDOM cannot calculate Tailwind media queries, so this verifies the actual
64+
// responsive utility classes that switch the footer layout at the md breakpoint.
65+
const responsiveLayout = footer.querySelector('.flex.flex-col.md\\:flex-row.md\\:items-start');
66+
67+
expect(responsiveLayout).toBeInTheDocument();
68+
expect(responsiveLayout).toHaveClass(
69+
'mx-auto',
70+
'flex',
71+
'max-w-6xl',
72+
'flex-col',
73+
'items-center',
74+
'justify-between',
75+
'gap-6',
76+
'text-sm',
77+
'md:flex-row',
78+
'md:items-start',
79+
'md:gap-0'
80+
);
7181
});
7282
});

app/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { Footer } from '@/app/components/Footer';
1414

1515
import { FeatureCard, FeatureCardsSection } from '@/components/FeatureCards';
1616
import { DiscordButton } from '@/components/DiscordButton';
17+
1718
import { WallOfLove } from '@/components/WallOfLove';
1819

1920
const Icons = {

components/WallOfLove.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import { useRef, useState, useEffect, type ReactNode } from 'react';
3+
import { useRef, useState, useEffect } from 'react';
44
import gsap from 'gsap';
55
import { ScrollTrigger } from 'gsap/ScrollTrigger';
66
import { motion } from 'framer-motion';

0 commit comments

Comments
 (0)