feat(ai-character): 커뮤니티 게시글 관리 기능 구현

This commit is contained in:
Yu Sung
2026-08-01 01:30:39 +09:00
parent f9ed4d0094
commit 20d38e38c0
21 changed files with 2292 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { expect, test, vi } from "vitest";
import { CommunityPostForm } from "@/features/community-posts/components/CommunityPostForm";
import { formatCommunityPostPrice, parseCommunityPostPrice } from "@/features/community-posts/components/community-post-form-helpers";
import type { ApiClient, ApiRequestOptions } from "@/shared/api/client";
type CapturedRequest = {
readonly body?: BodyInit | null;
readonly method?: string;
readonly path: string;
};
function createClient(requests: CapturedRequest[]): ApiClient {
return {
async request<Data>(options: ApiRequestOptions<Data>): Promise<Data> {
requests.push({ body: options.body, method: options.method, path: options.path });
return options.responseSchema.parse(null);
},
};
}
function renderForm(requests: CapturedRequest[]) {
const onCreated = vi.fn();
render(<CommunityPostForm apiClient={createClient(requests)} characterId="101" createCropSource={(file) => Promise.resolve({ file, height: 600, previewUrl: "blob:post", width: 800 })} onCreated={onCreated} />);
return onCreated;
}
async function submitWithPrice(value: string, requests: CapturedRequest[]) {
fireEvent.change(screen.getByLabelText("내용"), { target: { value: "가격 검증 게시글" } });
fireEvent.change(screen.getByLabelText("가격"), { target: { value } });
fireEvent.click(screen.getByRole("button", { name: "생성" }));
await screen.findByText("가격은 0 이상 99,999 이하 정수 캔으로 입력하세요.");
expect(requests.filter((request) => request.method === "POST")).toHaveLength(0);
}
test.each(["-1", "1.5"])("Community price keeps invalid raw input %s and blocks submit", async (value) => {
const requests: CapturedRequest[] = [];
const onCreated = renderForm(requests);
await submitWithPrice(value, requests);
expect(screen.getByLabelText("가격")).toHaveValue(value);
expect(onCreated).not.toHaveBeenCalled();
});
test.each(["0", "99,999캔"])("Community price allows boundary input %s", async (value) => {
const requests: CapturedRequest[] = [];
const onCreated = renderForm(requests);
fireEvent.change(screen.getByLabelText("내용"), { target: { value: "가격 경계 게시글" } });
fireEvent.change(screen.getByLabelText("가격"), { target: { value } });
fireEvent.click(screen.getByRole("button", { name: "생성" }));
await waitFor(() => expect(onCreated).toHaveBeenCalledTimes(1));
expect(requests.filter((request) => request.method === "POST")).toHaveLength(1);
});
test("Community price parser rejects negative and decimal raw input", () => {
expect(parseCommunityPostPrice("-1")).toBeNull();
expect(parseCommunityPostPrice("1.5")).toBeNull();
expect(formatCommunityPostPrice("-1")).toBe("-1");
expect(formatCommunityPostPrice("1.5")).toBe("1.5");
});