Skip to content

Commit 732a4a0

Browse files
committed
feat: add TechnicalExpertise component with category-based structure
- Create TechnicalExpertise section component with 4 skill categories - Implement responsive 2x2 grid layout (mobile-first design) - Add comprehensive test suite with 24 test cases covering: - Section structure and styling consistency with FeaturedProjects - Typography using JetBrains Mono for headings - Light/dark theme support with GitHub color palette - Category cards with hover effects and proper borders - Responsive grid behavior (1 col mobile, 2 cols desktop) - Export component from sections index for easy importing - Follow TDD approach: tests written first, implementation second - All 243 tests passing with no regressions Categories included: - Frontend Development - Backend Development - Mobile Development - DevOps & Tools
1 parent a8a33f6 commit 732a4a0

5 files changed

Lines changed: 279 additions & 2 deletions

File tree

src/app/__tests__/page.content.test.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ describe('Home Page Content Requirements', () => {
5454
expect(scrollIndicator).toBeInTheDocument()
5555
})
5656

57+
it('renders the Featured Projects section', () => {
58+
render(<Home />)
59+
const featuredProjectsHeading = screen.getByRole('heading', { name: /featured projects/i })
60+
expect(featuredProjectsHeading).toBeInTheDocument()
61+
})
62+
63+
it('renders the Technical Expertise section', () => {
64+
render(<Home />)
65+
const technicalExpertiseHeading = screen.getByRole('heading', { name: /technical expertise/i })
66+
expect(technicalExpertiseHeading).toBeInTheDocument()
67+
})
68+
5769
describe('Theme Toggle Positioning', () => {
5870
it('has theme toggle positioned correctly in header', () => {
5971
render(<Home />)

src/app/page.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { HeroSection, FeaturedProjects } from "@/components/sections";
1+
import { HeroSection, FeaturedProjects, TechnicalExpertise } from "@/components/sections";
22
import { ThemeToggle } from '@/components/ui/ThemeToggle';
33

44
export default function Home() {
@@ -32,6 +32,9 @@ export default function Home() {
3232

3333
{/* Featured Projects Section */}
3434
<FeaturedProjects />
35+
36+
{/* Technical Expertise Section */}
37+
<TechnicalExpertise />
3538
</div>
3639
);
3740
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Default skill categories
2+
const defaultCategories = [
3+
{
4+
title: "Frontend Development"
5+
},
6+
{
7+
title: "Backend Development"
8+
},
9+
{
10+
title: "Mobile Development"
11+
},
12+
{
13+
title: "DevOps & Tools"
14+
}
15+
];
16+
17+
export interface SkillCategory {
18+
title: string;
19+
}
20+
21+
export interface TechnicalExpertiseProps {
22+
categories?: SkillCategory[];
23+
}
24+
25+
export function TechnicalExpertise({ categories = defaultCategories }: TechnicalExpertiseProps) {
26+
return (
27+
<section
28+
className="py-20 bg-[#ffffff] dark:bg-[#0d1117]"
29+
aria-label="Technical Expertise"
30+
>
31+
<div
32+
className="max-w-6xl mx-auto px-4"
33+
data-testid="technical-expertise-container"
34+
>
35+
<h2 className="font-mono text-[2rem] md:text-[2.5rem] font-semibold text-[#24292f] dark:text-[#f0f6fc]">
36+
Technical Expertise
37+
</h2>
38+
39+
<div
40+
className="grid grid-cols-1 md:grid-cols-2 gap-8 mt-12"
41+
data-testid="categories-grid"
42+
>
43+
{categories.map((category, index) => (
44+
<div
45+
key={index}
46+
data-testid="category-card"
47+
className="bg-[#f6f8fa] dark:bg-[#21262d] border border-muted/20 rounded-lg p-6 hover:shadow-lg transition-all duration-300"
48+
>
49+
<h3 className="font-mono text-xl font-semibold text-[#24292f] dark:text-[#f0f6fc]">
50+
{category.title}
51+
</h3>
52+
</div>
53+
))}
54+
</div>
55+
</div>
56+
</section>
57+
);
58+
}
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import { render, screen } from "@testing-library/react";
2+
import { TechnicalExpertise } from "../TechnicalExpertise";
3+
4+
describe("TechnicalExpertise", () => {
5+
describe("Section Structure", () => {
6+
it("renders section with correct padding", () => {
7+
render(<TechnicalExpertise />);
8+
9+
const section = screen.getByRole("region", { name: /technical expertise/i });
10+
expect(section).toHaveClass("py-20");
11+
});
12+
13+
it("renders container with correct max-width and margins", () => {
14+
render(<TechnicalExpertise />);
15+
16+
const container = screen.getByTestId("technical-expertise-container");
17+
expect(container).toHaveClass("max-w-6xl", "mx-auto", "px-4");
18+
});
19+
20+
it("renders with light theme colors", () => {
21+
render(<TechnicalExpertise />);
22+
23+
const section = screen.getByRole("region", { name: /technical expertise/i });
24+
expect(section).toHaveClass("bg-[#ffffff]");
25+
});
26+
27+
it("renders with dark theme colors", () => {
28+
document.documentElement.classList.add("dark");
29+
30+
render(<TechnicalExpertise />);
31+
32+
const section = screen.getByRole("region", { name: /technical expertise/i });
33+
expect(section).toHaveClass("dark:bg-[#0d1117]");
34+
35+
document.documentElement.classList.remove("dark");
36+
});
37+
});
38+
39+
describe("Section Heading", () => {
40+
it("renders heading with correct text", () => {
41+
render(<TechnicalExpertise />);
42+
43+
const heading = screen.getByRole("heading", { level: 2 });
44+
expect(heading).toHaveTextContent("Technical Expertise");
45+
});
46+
47+
it("renders heading with JetBrains Mono font family", () => {
48+
render(<TechnicalExpertise />);
49+
50+
const heading = screen.getByRole("heading", { level: 2 });
51+
expect(heading).toHaveClass("font-mono");
52+
});
53+
54+
it("renders heading with correct sizes for desktop and mobile", () => {
55+
render(<TechnicalExpertise />);
56+
57+
const heading = screen.getByRole("heading", { level: 2 });
58+
expect(heading).toHaveClass("text-[2rem]", "md:text-[2.5rem]");
59+
});
60+
61+
it("renders heading with correct font weight", () => {
62+
render(<TechnicalExpertise />);
63+
64+
const heading = screen.getByRole("heading", { level: 2 });
65+
expect(heading).toHaveClass("font-semibold");
66+
});
67+
68+
it("renders heading with light theme text color", () => {
69+
render(<TechnicalExpertise />);
70+
71+
const heading = screen.getByRole("heading", { level: 2 });
72+
expect(heading).toHaveClass("text-[#24292f]");
73+
});
74+
75+
it("renders heading with dark theme text color", () => {
76+
document.documentElement.classList.add("dark");
77+
78+
render(<TechnicalExpertise />);
79+
80+
const heading = screen.getByRole("heading", { level: 2 });
81+
expect(heading).toHaveClass("dark:text-[#f0f6fc]");
82+
83+
document.documentElement.classList.remove("dark");
84+
});
85+
});
86+
87+
describe("Category Grid", () => {
88+
it("renders grid container with correct responsive classes", () => {
89+
render(<TechnicalExpertise />);
90+
91+
const grid = screen.getByTestId("categories-grid");
92+
expect(grid).toHaveClass("grid", "grid-cols-1", "md:grid-cols-2");
93+
});
94+
95+
it("renders grid with correct gap spacing", () => {
96+
render(<TechnicalExpertise />);
97+
98+
const grid = screen.getByTestId("categories-grid");
99+
expect(grid).toHaveClass("gap-8");
100+
});
101+
102+
it("renders grid with correct margin top from heading", () => {
103+
render(<TechnicalExpertise />);
104+
105+
const grid = screen.getByTestId("categories-grid");
106+
expect(grid).toHaveClass("mt-12");
107+
});
108+
});
109+
110+
describe("Category Cards", () => {
111+
it("renders all 4 category cards", () => {
112+
render(<TechnicalExpertise />);
113+
114+
const categoryCards = screen.getAllByTestId("category-card");
115+
expect(categoryCards).toHaveLength(4);
116+
});
117+
118+
it("renders Frontend Development category", () => {
119+
render(<TechnicalExpertise />);
120+
121+
expect(screen.getByText("Frontend Development")).toBeInTheDocument();
122+
});
123+
124+
it("renders Backend Development category", () => {
125+
render(<TechnicalExpertise />);
126+
127+
expect(screen.getByText("Backend Development")).toBeInTheDocument();
128+
});
129+
130+
it("renders Mobile Development category", () => {
131+
render(<TechnicalExpertise />);
132+
133+
expect(screen.getByText("Mobile Development")).toBeInTheDocument();
134+
});
135+
136+
it("renders DevOps & Tools category", () => {
137+
render(<TechnicalExpertise />);
138+
139+
expect(screen.getByText("DevOps & Tools")).toBeInTheDocument();
140+
});
141+
142+
it("renders category cards with correct background colors", () => {
143+
render(<TechnicalExpertise />);
144+
145+
const categoryCards = screen.getAllByTestId("category-card");
146+
categoryCards.forEach(card => {
147+
expect(card).toHaveClass("bg-[#f6f8fa]");
148+
});
149+
});
150+
151+
it("renders category cards with dark theme background colors", () => {
152+
document.documentElement.classList.add("dark");
153+
154+
render(<TechnicalExpertise />);
155+
156+
const categoryCards = screen.getAllByTestId("category-card");
157+
categoryCards.forEach(card => {
158+
expect(card).toHaveClass("dark:bg-[#21262d]");
159+
});
160+
161+
document.documentElement.classList.remove("dark");
162+
});
163+
164+
it("renders category cards with correct border styling", () => {
165+
render(<TechnicalExpertise />);
166+
167+
const categoryCards = screen.getAllByTestId("category-card");
168+
categoryCards.forEach(card => {
169+
expect(card).toHaveClass("border", "border-muted/20", "rounded-lg");
170+
});
171+
});
172+
173+
it("renders category cards with correct padding", () => {
174+
render(<TechnicalExpertise />);
175+
176+
const categoryCards = screen.getAllByTestId("category-card");
177+
categoryCards.forEach(card => {
178+
expect(card).toHaveClass("p-6");
179+
});
180+
});
181+
182+
it("renders category cards with hover effects", () => {
183+
render(<TechnicalExpertise />);
184+
185+
const categoryCards = screen.getAllByTestId("category-card");
186+
categoryCards.forEach(card => {
187+
expect(card).toHaveClass("hover:shadow-lg", "transition-all", "duration-300");
188+
});
189+
});
190+
});
191+
192+
describe("Responsive Design", () => {
193+
it("has mobile-first responsive grid classes", () => {
194+
render(<TechnicalExpertise />);
195+
196+
const grid = screen.getByTestId("categories-grid");
197+
// Should start with 1 column on mobile
198+
expect(grid).toHaveClass("grid-cols-1");
199+
// Then 2 columns on medium screens
200+
expect(grid).toHaveClass("md:grid-cols-2");
201+
});
202+
});
203+
});

src/components/sections/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from "./HeroSection";
2-
export * from "./FeaturedProjects";
2+
export * from "./FeaturedProjects";
3+
export * from "./TechnicalExpertise";

0 commit comments

Comments
 (0)