Skip to content

Commit

Permalink
Merge pull request #357 from oduck-team/test/356
Browse files Browse the repository at this point in the history
test: 프로필 페이지 테스트코드 작성
  • Loading branch information
presentKey authored Jan 30, 2024
2 parents 414369c + bf75767 commit 58eeb47
Show file tree
Hide file tree
Showing 9 changed files with 1,066 additions and 1,779 deletions.
2,519 changes: 745 additions & 1,774 deletions package-lock.json

Large diffs are not rendered by default.

33 changes: 31 additions & 2 deletions src/__test__/customRender.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { ThemeProvider } from "@emotion/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { RenderOptions, render } from "@testing-library/react";
import { PropsWithChildren } from "react";

import { OduckApiContext } from "@/contexts/OduckApiContext";
import { theme } from "@/styles/theme";

export default function customRender(
Expand All @@ -11,6 +13,33 @@ export default function customRender(
return render(ui, { wrapper: RenderWithProviders, ...options });
}

function RenderWithProviders({ children }: PropsWithChildren) {
return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
export function RenderWithProviders(
{ children }: PropsWithChildren,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
oduck: any,
) {
const testClient = createTestQueryClient();
return (
<OduckApiContext.Provider value={oduck}>
<QueryClientProvider client={testClient}>
<ThemeProvider theme={theme}>
{children}
{/* </IconContext.Provider> */}
</ThemeProvider>
</QueryClientProvider>
</OduckApiContext.Provider>
);
}

function createTestQueryClient() {
return new QueryClient({
defaultOptions: {
queries: { retry: false },
},
logger: {
log: console.log,
warn: console.warn,
error: () => {},
},
});
}
33 changes: 33 additions & 0 deletions src/components/Avatar/tests/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { cleanup, screen } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";

import customRender from "@/__test__/customRender";

import Avatar from "..";

describe("Avatar", () => {
const profile = {
name: "이름입니다",
src: "http://image/",
};

afterEach(() => cleanup());

it("이미지 src를 전달한 경우", () => {
customRender(<Avatar userName={profile.name} src={profile.src} />);

const image = screen.getByRole<HTMLImageElement>("img");

expect(image.src).toBe(profile.src);
expect(image.alt).toBe(profile.name);
});

it("이미지 src를 전달하지 않은 경우, userName 앞 두글자가 표시된다.", () => {
customRender(<Avatar userName={profile.name} />);

const image = screen.queryByRole("img");

expect(image).not.toBeInTheDocument();
expect(screen.getByText("이름")).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import {

export interface ProfileArtProps {
src?: string;
userName?: string;
}

export default function ProfileArt({ src }: ProfileArtProps) {
export default function ProfileArt({ src, userName }: ProfileArtProps) {
return (
<ProfileArtContainer>
{!src && <DefaultImage />}
{src && <CustomImage src={src} />}
{src && <CustomImage src={src} alt={`${userName}님의 배경 이미지`} />}
</ProfileArtContainer>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";

import customRender from "@/__test__/customRender";

import ProfileImageSection from "..";

describe("ProfileArt", () => {
const profile = {
name: "이름",
src: "http://image/",
};

afterEach(() => cleanup());

it("이미지 src를 전달한 경우", () => {
render(
<ProfileImageSection>
<ProfileImageSection.Art src={profile.src} userName={profile.name} />
</ProfileImageSection>,
);

const image = screen.getByRole<HTMLImageElement>("img");

expect(image.src).toBe(profile.src);
expect(image.alt).toBe(`${profile.name}님의 배경 이미지`);
});

it("이미지 src를 전달하지 않은 경우", () => {
customRender(
<ProfileImageSection>
<ProfileImageSection.Art />
</ProfileImageSection>,
);

const image = screen.queryByRole("img");

expect(image).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { cleanup, fireEvent, screen } from "@testing-library/react";
import { MemoryRouter, Route, Routes } from "react-router-dom";
import { afterEach, describe, expect, it } from "vitest";

import customRender from "@/__test__/customRender";

import ProfileImageSection from "..";

describe("ProfileSetupButton", () => {
afterEach(() => cleanup());

describe("내 프로필인 경우", () => {
it("설정 버튼을 클릭하면 '프로필 수정' 버튼이 렌더링 된다.", async () => {
customRender(
<MemoryRouter>
<ProfileImageSection>
<ProfileImageSection.ProfileSetupButton isMine={true} />
</ProfileImageSection>
</MemoryRouter>,
);

const button = screen.getByRole("button");
fireEvent.click(button);

const editButton = screen.getByLabelText("프로필 수정");
expect(editButton).toBeInTheDocument();
});

it("'프로필 수정' 버튼을 클릭하면, '프로필 수정' 페이지로 이동한다.", () => {
function RouteProfileEditPage() {
return <section>프로필 수정 페이지</section>;
}

customRender(
<MemoryRouter initialEntries={["/"]}>
<Routes>
<Route
path="/"
element={
<ProfileImageSection>
<ProfileImageSection.ProfileSetupButton isMine={true} />
</ProfileImageSection>
}
/>
<Route path="/profile/edit" element={<RouteProfileEditPage />} />
</Routes>
</MemoryRouter>,
);

const button = screen.getByRole("button");
fireEvent.click(button);

const editButton = screen.getByLabelText("프로필 수정");
fireEvent.click(editButton);

expect(screen.getByText("프로필 수정 페이지")).toBeInTheDocument();
});
});

it("다른 사용자의 프로필 설정 버튼 클릭하면, '신고하기' 버튼이 렌더링 된다.", () => {
customRender(
<MemoryRouter>
<ProfileImageSection>
<ProfileImageSection.ProfileSetupButton isMine={false} />
</ProfileImageSection>
</MemoryRouter>,
);

const button = screen.getByRole("button");
fireEvent.click(button);

const reportButton = screen.getByLabelText("신고하기");

expect(reportButton).toBeInTheDocument();
});
});
3 changes: 3 additions & 0 deletions src/features/users/routes/Edit/EditForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export default function EditForm({
<ProfileImageSection>
<ProfileImageSection.Art
src={previewArt ? previewArt : backgroundImage}
userName={name}
/>
<ImageEditButton
croppedImage={previewArt}
Expand Down Expand Up @@ -123,6 +124,7 @@ export default function EditForm({
<TextInput
required
name="name"
aria-label="닉네임 입력"
value={form.name}
maxLength={10}
message={status.message}
Expand All @@ -135,6 +137,7 @@ export default function EditForm({
<Title>자기소개</Title>
<Textarea
name="description"
aria-label="자기소개 입력"
value={form.description}
placeholder="자기소개를 적어보세요(최대 100자까지 가능합니다)"
maxLength={100}
Expand Down
134 changes: 134 additions & 0 deletions src/features/users/routes/Edit/tests/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import { HelmetProvider } from "react-helmet-async";
import { MemoryRouter, Route, Routes } from "react-router-dom";
import { afterEach, describe, expect, it, vi } from "vitest";

import { RenderWithProviders } from "@/__test__/customRender";
import { ToastContextProvider } from "@/contexts/ToastContext";

import ProfileEdit from "..";

describe("Profile Edit 페이지", () => {
const fakeApi = {
profile: {
getProfile: vi.fn(),
},
};

afterEach(() => {
fakeApi.profile.getProfile.mockReset();
cleanup();
});

const profile = {
memberId: 1,
name: "testName",
description: "자기소개 테스트",
backgroundImage: "",
thumbnail: "",
activity: {
reviews: 1,
bookmarks: 2,
likes: 3,
point: 0,
},
isMine: true,
};

it("사용자의 현재 닉네임과 자기소개가 input에 입력되어 있다.", async () => {
fakeApi.profile.getProfile.mockImplementation(() => profile);

render(
RenderWithProviders(
{
children: (
<HelmetProvider>
<ToastContextProvider>
<MemoryRouter initialEntries={["/profile/edit"]}>
<Routes>
<Route path="/profile/edit" element={<ProfileEdit />} />
</Routes>
</MemoryRouter>
</ToastContextProvider>
</HelmetProvider>
),
},
fakeApi,
),
);

const name = await screen.findByLabelText<HTMLInputElement>("닉네임 입력");
const description = await screen.findByLabelText<HTMLTextAreaElement>(
"자기소개 입력",
);

expect(name.value).toBe(profile.name);
expect(description.value).toBe(profile.description);
});

it("닉네임은 10글자 제한이므로, 신규 사용자 닉네임은 input에 공백으로 처리되어 있다.", async () => {
const newUserProfile = { ...profile, name: "영역전개하는_TEST_1234" };
fakeApi.profile.getProfile.mockImplementation(() => newUserProfile);

render(
RenderWithProviders(
{
children: (
<HelmetProvider>
<ToastContextProvider>
<MemoryRouter initialEntries={["/profile/edit"]}>
<Routes>
<Route path="/profile/edit" element={<ProfileEdit />} />
</Routes>
</MemoryRouter>
</ToastContextProvider>
</HelmetProvider>
),
},
fakeApi,
),
);

const nameInput = await screen.findByLabelText<HTMLInputElement>(
"닉네임 입력",
);

expect(nameInput.value).toBe("");
});

it("자기소개 100글자 제한", async () => {
fakeApi.profile.getProfile.mockImplementation(() => profile);

render(
RenderWithProviders(
{
children: (
<HelmetProvider>
<ToastContextProvider>
<MemoryRouter initialEntries={["/profile/edit"]}>
<Routes>
<Route path="/profile/edit" element={<ProfileEdit />} />
</Routes>
</MemoryRouter>
</ToastContextProvider>
</HelmetProvider>
),
},
fakeApi,
),
);

const descriptionInput = await screen.findByLabelText<HTMLTextAreaElement>(
"자기소개 입력",
);

fireEvent.change(descriptionInput, { target: { value: "T".repeat(99) } });
expect(descriptionInput.value.length).toBe(99);

fireEvent.change(descriptionInput, { target: { value: "T".repeat(100) } });
expect(descriptionInput.value.length).toBe(100);

fireEvent.change(descriptionInput, { target: { value: "T".repeat(101) } });
expect(descriptionInput.value.length).toBe(100);
});
});
2 changes: 1 addition & 1 deletion src/features/users/routes/Profile/AboutMe/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default function AboutMe({
return (
<>
<ProfileImageSection>
<ProfileImageSection.Art src={backgroundImage} />
<ProfileImageSection.Art src={backgroundImage} userName={name} />
<ProfileImageSection.ProfileSetupButton isMine={isMine} />
<ProfileImageSection.ProfileAvatar>
<ProfileAvatar.Avatar src={thumbnail} userName={name} size="xl" />
Expand Down

0 comments on commit 58eeb47

Please sign in to comment.