Skip to content
Open
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
16 changes: 9 additions & 7 deletions client/src/__tests__/__mocks__/components/ui/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { ReactNode } from "react";

export const mockDialog = {
Dialog: ({ children, open }: { children: React.ReactNode; open: boolean }) => (open ? <div>{children}</div> : null),
DialogContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
DialogHeader: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
DialogTitle: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
DialogDescription: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
DialogFooter: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
};
Dialog: ({ children, open }: { children: ReactNode; open: boolean }) => (open ? <div>{children}</div> : null),
DialogContent: ({ children }: { children: ReactNode }) => <div>{children}</div>,
DialogHeader: ({ children }: { children: ReactNode }) => <div>{children}</div>,
DialogTitle: ({ children }: { children: ReactNode }) => <div>{children}</div>,
DialogDescription: ({ children }: { children: ReactNode }) => <div>{children}</div>,
DialogFooter: ({ children }: { children: ReactNode }) => <div>{children}</div>,
};
6 changes: 6 additions & 0 deletions client/src/__tests__/__mocks__/components/ui/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ReactNode } from "react";

export const mockSidebar = {
Sidebar: ({ children }: { children: ReactNode }) => <div data-testid="sidebar">{children}</div>,
SidebarContent: ({ children }: { children: ReactNode }) => <div data-testid="sidebar-content">{children}</div>,
};
28 changes: 28 additions & 0 deletions client/src/__tests__/__mocks__/external/recharts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export const mockRecharts = {
ResponsiveContainer: ({ children }: { children: React.ReactNode }) => (
<div data-testid="responsive-container">{children}</div>
),
BarChart: ({ children }: { children: React.ReactNode }) => (
<div data-testid="bar-chart" role="img" aria-label="bar chart">
{children}
</div>
),
CartesianGrid: () => <div data-testid="cartesian-grid">Grid</div>,
XAxis: () => (
<div data-testid="x-axis">
<div data-testid="x-axis-tick">Item 1</div>
<div data-testid="x-axis-tick">Item 2</div>
</div>
),
Bar: ({ fill }: { fill: string }) => (
<div data-testid="bar" role="img" aria-label="bar">
{fill}
</div>
),
Tooltip: () => <div data-testid="tooltip">Tooltip</div>,
Legend: () => <div data-testid="legend">Legend</div>,
PieChart: ({ children }: { children: React.ReactNode }) => <div data-testid="pie-chart">{children}</div>,
Pie: ({ children }: { children: React.ReactNode }) => <div data-testid="pie">{children}</div>,
Cell: ({ fill }: { fill: string }) => <div data-testid="pie-cell" data-fill={fill} />,
LabelList: () => <div data-testid="label-list">Labels</div>,
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,4 @@ describe("PostCardImage", () => {
expect(fallbackContainer).toBeInTheDocument();
expect(screen.queryByTestId("lazy-image")).not.toBeInTheDocument();
});

it("container에 올바른 class가 적용되는지 확인", () => {
render(<PostCardImage alt="테스트 이미지" />);

const container = screen.getByTestId("image-container");
expect(container).toHaveClass("h-[120px]", "rounded-t-xl");
});
});
82 changes: 82 additions & 0 deletions client/src/__tests__/components/common/chart/BarChartItem.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { describe, it, expect } from "vitest";

import BarChartItem from "@/components/chart/BarChartItem";

import { ChartType } from "@/types/chart";
import { render, screen } from "@testing-library/react";

describe("BarChartItem", () => {
const data: ChartType[] = [
{ id: 1, title: "Item 1", viewCount: 100 },
{ id: 2, title: "Item 2", viewCount: 200 },
];

it("ResizeObserver가 정상적으로 동작한다", () => {
global.ResizeObserver = class {
observe() {}
unobserve() {}
disconnect() {}
};

render(<BarChartItem data={data} title="Test Title" description="Test Description" color={true} />);
expect(screen.getByText("Test Title")).toBeInTheDocument();
});

it("ResizeObserver가 정의되지 않아도 정상적으로 렌더링된다", () => {
render(<BarChartItem data={data} title="Test Title" description="Test Description" color={true} />);
expect(screen.getByText("Test Title")).toBeInTheDocument();
});

it("제목과 설명이 올바르게 렌더링된다", () => {
render(<BarChartItem data={data} title="Test Title" description="Test Description" color={true} />);
expect(screen.getByText("Test Title")).toBeInTheDocument();
expect(screen.getByText("Test Description")).toBeInTheDocument();
});

it("리사이즈 시 컴포넌트 너비가 업데이트된다", () => {
global.ResizeObserver = class {
observe() {}
unobserve() {}
disconnect() {}
};

const { container } = render(
<BarChartItem data={data} title="Test Title" description="Test Description" color={true} />
);
const card = container.querySelector(".w-full");

Object.defineProperty(card, "offsetWidth", { configurable: true, value: 500 });
window.dispatchEvent(new Event("resize"));

expect(screen.getByText("Test Title")).toBeInTheDocument();
});

it("데이터가 차트에 올바르게 표시된다", () => {
render(<BarChartItem data={data} title="Test Title" description="Test Description" color={true} />);

const ticks = screen.getAllByTestId("x-axis-tick");

expect(ticks[0]).toHaveTextContent("Item 1");
expect(ticks[1]).toHaveTextContent("Item 2");
});

describe("텍스트 자르기 함수", () => {
const truncateText = (text: string, componentWidth: number) => {
const charWidth = 55;
const maxChars = Math.floor(componentWidth / charWidth);
return text.length > maxChars ? `${text.slice(0, Math.max(0, maxChars - 3))}...` : text;
};

it("텍스트가 너무 길 경우 잘라서 표시한다", () => {
const longText = "This is a very long text";
const result = truncateText(longText, 200);
expect(result).toBe("...");
});

it("텍스트가 짧을 경우 그대로 표시한다", () => {
const shortText = "Hi";
const result = truncateText(shortText, 200);
expect(result).toBe("Hi");
});
});
});
97 changes: 97 additions & 0 deletions client/src/__tests__/components/common/chart/Chart.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { describe, it, expect, vi, beforeEach } from "vitest";

import Chart from "@/components/chart/Chart";

import { useChart } from "@/hooks/queries/useChart";

import { useMediaStore } from "@/store/useMediaStore";
import { render, screen, act } from "@testing-library/react";

vi.mock("@/hooks/queries/useChart");
vi.mock("@/store/useMediaStore");
vi.mock("@/components/chart/BarChartItem", () => ({
default: () => <div data-testid="bar-chart-item">BarChartItem</div>,
}));
vi.mock("@/components/chart/PieChartItem", () => ({
default: () => <div data-testid="pie-chart-item">PieChartItem</div>,
}));
vi.mock("@/components/chart/ChartSkeleton", () => ({
default: () => <div data-testid="chart-skeleton">ChartSkeleton</div>,
}));

describe("Chart", () => {
beforeEach(() => {
vi.resetAllMocks();
});

it("데이터 로딩 중일 때 ChartSkeleton을 렌더링한다", () => {
vi.mocked(useChart).mockReturnValue({
data: undefined,
isLoading: true,
error: null,
});
vi.mocked(useMediaStore).mockReturnValue(() => false);

render(<Chart />);
expect(screen.getByTestId("chart-skeleton")).toBeInTheDocument();
});

it("에러가 있을 때 에러 메시지를 렌더링한다", () => {
vi.mocked(useChart).mockReturnValue({
data: undefined,
isLoading: false,
error: new Error("Failed to fetch"),
});
vi.mocked(useMediaStore).mockReturnValue(() => false);

render(<Chart />);
expect(screen.getByText("Error loading data")).toBeInTheDocument();
});

it("데이터가 있고 isMobile이 false일 때 DesktopChart 레이아웃이 올바르게 적용된다", async () => {
vi.mocked(useChart).mockReturnValue({
data: {
chartAll: { message: "Success", data: [] },
chartToday: { message: "Success", data: [] },
chartPlatform: { message: "Success", data: [] },
},
isLoading: false,
error: null,
});
vi.mocked(useMediaStore).mockReturnValue(false);

await act(async () => {
render(<Chart />);
});

const container = screen.getAllByText("BarChartItem")[0].closest(".p-8");
expect(container).toBeInTheDocument();
expect(container).toHaveClass("p-8");

const barCharts = screen.getAllByTestId("bar-chart-item");
expect(barCharts).toHaveLength(2);

const pieChart = screen.getByTestId("pie-chart-item");
expect(pieChart).toBeInTheDocument();
});

it("데이터가 있고 isMobile이 true일 때 MobileChart를 렌더링한다", async () => {
vi.mocked(useChart).mockReturnValue({
data: {
chartAll: { message: "Success", data: [] },
chartToday: { message: "Success", data: [] },
chartPlatform: { message: "Success", data: [] },
},
isLoading: false,
error: null,
});
vi.mocked(useMediaStore).mockReturnValue(() => true);

await act(async () => {
render(<Chart />);
});

expect(screen.getAllByTestId("bar-chart-item")).toHaveLength(2);
expect(screen.getByTestId("pie-chart-item")).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { describe, it, expect, vi } from "vitest";

import ChartSkeleton from "@/components/chart/ChartSkeleton";

import { render, screen } from "@testing-library/react";

vi.mock("@/components/chart/BarChartItem", () => ({
default: vi.fn(({ title, description, data, color }) => (
<div data-testid="bar-chart-item" data-title={title} data-description={description} data-color={color}>
<div>Mock BarChartItem</div>
<div>Data Length: {data.length}</div>
</div>
)),
}));
vi.mock("@/components/chart/PieChartItem", () => ({
default: vi.fn(({ title, data }) => (
<div data-testid="pie-chart-item" data-title={title}>
<div>Mock PieChartItem</div>
<div>Data Length: {data.length}</div>
</div>
)),
}));

describe("ChartSkeleton", () => {
it("should render the component with correct structure", () => {
render(<ChartSkeleton />);

const container = screen.getByTestId("chart-skeleton-container");
expect(container).toHaveClass("p-8");
});

describe("BarChartItem 렌더링", () => {
it("전체 조회수 차트가 올바른 props로 렌더링되어야 한다", () => {
render(<ChartSkeleton />);

const totalViewsChart = screen.getAllByTestId("bar-chart-item")[0];
expect(totalViewsChart).toHaveAttribute("data-title", "전체 조회수");
expect(totalViewsChart).toHaveAttribute("data-description", "전체 조회수 TOP5");
expect(totalViewsChart).toHaveAttribute("data-color", "true");
});

it("오늘의 조회수 차트가 올바른 props로 렌더링되어야 한다", () => {
render(<ChartSkeleton />);

const todayViewsChart = screen.getAllByTestId("bar-chart-item")[1];
expect(todayViewsChart).toHaveAttribute("data-title", "오늘의 조회수");
expect(todayViewsChart).toHaveAttribute("data-description", "금일 조회수 TOP5");
expect(todayViewsChart).toHaveAttribute("data-color", "false");
});
});

describe("PieChartItem 렌더링", () => {
it("플랫폼별 블로그 수 차트가 올바른 props로 렌더링되어야 한다", () => {
render(<ChartSkeleton />);

const platformChart = screen.getByTestId("pie-chart-item");
expect(platformChart).toHaveAttribute("data-title", "플랫폼별 블로그 수");
});
});

describe("데이터 전달", () => {
it("모든 차트 컴포넌트에 빈 데이터 배열이 전달되어야 한다", () => {
render(<ChartSkeleton />);

const barCharts = screen.getAllByTestId("bar-chart-item");
const pieChart = screen.getByTestId("pie-chart-item");

barCharts.forEach((chart) => {
expect(chart.textContent).toContain("Data Length: 0");
});
expect(pieChart.textContent).toContain("Data Length: 0");
});
});
});
43 changes: 43 additions & 0 deletions client/src/__tests__/components/common/chart/ChartTab.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { describe, it, expect, vi } from "vitest";

import ChartTab from "@/components/chart/ChartTab";

import { act, render, screen } from "@testing-library/react";

vi.mock("@/components/chart/Chart", () => ({
default: vi.fn(() => <div data-testid="chart">Chart Component</div>),
}));
vi.mock("@/components/chart/ChartSkeleton", () => ({
default: vi.fn((props) => <div data-testid={props["data-testid"]}>Chart Skeleton</div>),
}));

describe("ChartTab 컴포넌트", () => {
it("초기 로딩시 ChartSkeleton을 표시한다", async () => {
render(<ChartTab />);

expect(screen.getByTestId("chart-skeleton")).toBeInTheDocument();
});

it("Chart 컴포넌트가 로드되면 ChartSkeleton이 사라진다", async () => {
render(<ChartTab />);

const chart = await screen.findByTestId("chart");

expect(chart).toBeInTheDocument();
expect(screen.queryByTestId("chart-skeleton")).not.toBeInTheDocument();
});

it("Chart 컴포넌트 로딩 과정이 정상적으로 동작한다", async () => {
await act(async () => {
render(<ChartTab />);
});

const skeleton = screen.getByTestId("chart");
expect(skeleton).toBeInTheDocument();

const chart = await screen.findByTestId("chart");
expect(chart).toBeInTheDocument();

expect(screen.queryByTestId("chart-skeleton")).not.toBeInTheDocument();
});
});
Loading
Loading