Skip to content

Commit 385e586

Browse files
committed
refactor(blog): 시리즈 허브 카드 구조 정리
시리즈 제목 앞의 중복 배지를 제거하고 에피소드 링크 여백을 정리했어요. 변경된 카드 구조를 검증하는 컴포넌트 테스트를 추가했어요.
1 parent 3d08c9c commit 385e586

2 files changed

Lines changed: 91 additions & 8 deletions

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { render, screen } from '@testing-library/react';
2+
import { describe, expect, it, vi } from 'vitest';
3+
import type { SeriesSummary } from '@/features/blog/model/series-group';
4+
import SeriesHubCard from './SeriesHubCard';
5+
6+
vi.mock('./SeriesTrackedLink', () => ({
7+
default: ({
8+
href,
9+
children,
10+
className,
11+
}: {
12+
href: string;
13+
children: React.ReactNode;
14+
className?: string;
15+
}) => (
16+
<a href={href} className={className}>
17+
{children}
18+
</a>
19+
),
20+
}));
21+
22+
const summary: SeriesSummary = {
23+
id: 'redis-deep-dive',
24+
title: 'Redis 완전정복',
25+
latestDate: '2026-03-10',
26+
firstPostSlug: 'redis-1',
27+
postCount: 2,
28+
totalReadingMinutes: 18,
29+
posts: [
30+
{
31+
slug: 'redis-1',
32+
title: 'Redis 1편',
33+
description: '기초를 다뤄요',
34+
date: '2026-03-01',
35+
category: 'Tech',
36+
tags: ['Redis'],
37+
readingTime: 8,
38+
series: {
39+
id: 'redis-deep-dive',
40+
title: 'Redis 완전정복',
41+
order: 1,
42+
},
43+
},
44+
{
45+
slug: 'redis-2',
46+
title: 'Redis 2편',
47+
description: '심화를 다뤄요',
48+
date: '2026-03-10',
49+
category: 'Tech',
50+
tags: ['Redis'],
51+
readingTime: 10,
52+
series: {
53+
id: 'redis-deep-dive',
54+
title: 'Redis 완전정복',
55+
order: 2,
56+
},
57+
},
58+
],
59+
};
60+
61+
describe('SeriesHubCard', () => {
62+
it('renders series title and links without the redundant series badge', () => {
63+
render(<SeriesHubCard summary={summary} seriesIndex={0} />);
64+
65+
expect(
66+
screen.getByRole('heading', { level: 2, name: 'Redis 완전정복' })
67+
).toBeInTheDocument();
68+
expect(
69+
screen.getByRole('link', { name: / /i })
70+
).toHaveAttribute('href', '/blog/redis-1');
71+
const firstEpisodeLink = screen.getByRole('link', {
72+
name: /1\.?\s*Redis 1/i,
73+
});
74+
75+
expect(firstEpisodeLink).toHaveAttribute('href', '/blog/redis-1');
76+
expect(firstEpisodeLink).toHaveClass('min-h-10', 'py-1.5');
77+
expect(screen.queryByText('Series')).not.toBeInTheDocument();
78+
});
79+
});

src/features/blog/ui/components/SeriesHubCard.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,23 @@ interface SeriesHubCardProps {
77
seriesIndex: number;
88
}
99

10-
export default function SeriesHubCard({ summary, seriesIndex }: SeriesHubCardProps) {
10+
export default function SeriesHubCard({
11+
summary,
12+
seriesIndex,
13+
}: SeriesHubCardProps) {
1114
const metaText = `총 ${summary.postCount}편 · 총 ${summary.totalReadingMinutes}분 · 최근 업데이트 ${formatSeriesDate(summary.latestDate)}`;
1215
const firstPostOrder = summary.posts[0]?.series?.order;
1316

1417
return (
1518
<section className="rounded-[var(--radius-md)] border border-[var(--color-border)] bg-[var(--color-bg-primary)] p-6">
1619
<div className="flex flex-wrap items-start justify-between gap-3">
1720
<div>
18-
<span className="inline-flex rounded-full border border-[var(--color-category-series-border)] bg-[var(--color-category-series-bg)] px-2 py-1 text-xs font-medium text-[var(--color-category-series-text)]">
19-
Series
20-
</span>
21-
<h2 className="mt-2 text-xl font-semibold text-[var(--color-text-primary)]">
21+
<h2 className="text-xl font-semibold text-[var(--color-text-primary)]">
2222
{summary.title}
2323
</h2>
24-
<p className="mt-1 text-sm text-[var(--color-text-tertiary)]">{metaText}</p>
24+
<p className="mt-1 text-sm text-[var(--color-text-tertiary)]">
25+
{metaText}
26+
</p>
2527
</div>
2628

2729
{summary.firstPostSlug && (
@@ -55,9 +57,11 @@ export default function SeriesHubCard({ summary, seriesIndex }: SeriesHubCardPro
5557
postSlug={post.slug}
5658
episodeOrder={order}
5759
seriesIndex={seriesIndex}
58-
className="flex min-h-11 items-center gap-3 rounded-[var(--radius-sm)] px-3 py-2 text-sm text-[var(--color-text-secondary)] transition-colors hover:bg-[var(--color-grey-50)] hover:text-[var(--color-text-primary)]"
60+
className="flex min-h-10 items-center gap-3 rounded-[var(--radius-sm)] px-3 py-1.5 text-sm text-[var(--color-text-secondary)] transition-colors hover:bg-[var(--color-grey-50)] hover:text-[var(--color-text-primary)]"
5961
>
60-
<span className="w-6 text-[var(--color-text-tertiary)]">{order}.</span>
62+
<span className="w-6 text-[var(--color-text-tertiary)]">
63+
{order}.
64+
</span>
6165
<span className="flex-1 truncate">{post.title}</span>
6266
</SeriesTrackedLink>
6367
</li>

0 commit comments

Comments
 (0)