사용자 로그인, 가입 처리
This commit is contained in:
@@ -96,6 +96,15 @@ vi.mock("next/link", () => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("next/navigation", () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
useRouter: () => ({
|
||||
push: vi.fn(),
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe("EditorPage - 프로젝트 저장/불러오기", () => {
|
||||
it("프로젝트 저장 버튼 클릭 시 로컬스토리지와 서버에 상태를 저장해야 하고, projectConfig 와 제목/주소 입력이 동기화되어야 한다", async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
@@ -135,7 +144,11 @@ describe("EditorPage - 프로젝트 저장/불러오기", () => {
|
||||
fireEvent.click(saveButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
fetchMock.mock.calls.some(
|
||||
([url, options]: any[]) => url === "/api/projects" && options?.method === "POST",
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
const localKey = "pb:project:save-test-slug";
|
||||
@@ -147,7 +160,11 @@ describe("EditorPage - 프로젝트 저장/불러오기", () => {
|
||||
expect(Array.isArray(parsed.contentJson)).toBe(true);
|
||||
expect(parsed.contentJson[0].id).toBe("blk_1");
|
||||
|
||||
const [url, options] = fetchMock.mock.calls[0] as any;
|
||||
const projectCall = fetchMock.mock.calls.find(
|
||||
([url, options]: any[]) => url === "/api/projects" && options?.method === "POST",
|
||||
) as any;
|
||||
|
||||
const [url, options] = projectCall;
|
||||
expect(url).toBe("/api/projects");
|
||||
expect(options.method).toBe("POST");
|
||||
expect(options.headers["Content-Type"]).toBe("application/json");
|
||||
@@ -162,6 +179,55 @@ describe("EditorPage - 프로젝트 저장/불러오기", () => {
|
||||
expect(message).toBeTruthy();
|
||||
});
|
||||
|
||||
it("서버가 409 를 반환하면 이미 사용 중인 프로젝트 주소라는 에러 메시지를 보여줘야 한다", async () => {
|
||||
const fetchMock = vi.fn().mockImplementation((input: any, init?: any) => {
|
||||
const url = typeof input === "string" ? input : input.url;
|
||||
const method = init?.method ?? "GET";
|
||||
|
||||
if (url === "/api/projects" && method === "POST") {
|
||||
return Promise.resolve({
|
||||
ok: false,
|
||||
status: 409,
|
||||
json: async () => ({ message: "이미 다른 사용자가 사용 중인 프로젝트 주소입니다." }),
|
||||
} as any);
|
||||
}
|
||||
|
||||
// 인증 가드용 /api/auth/me 등은 성공 응답만 내려주면 된다.
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({}),
|
||||
} as any);
|
||||
});
|
||||
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
const { rerender } = render(<EditorPage />);
|
||||
|
||||
const menuButton = screen.getByText("메뉴");
|
||||
fireEvent.click(menuButton);
|
||||
|
||||
const projectMenuItem = screen.getByText("프로젝트 저장/불러오기");
|
||||
fireEvent.click(projectMenuItem);
|
||||
|
||||
const modalTitle = screen.getByText("프로젝트 저장 / 불러오기");
|
||||
const projectModal = (modalTitle.parentElement?.parentElement ?? document.body) as HTMLElement;
|
||||
|
||||
const titleInput = within(projectModal).getByLabelText("프로젝트 제목") as HTMLInputElement;
|
||||
const slugInput = within(projectModal).getByLabelText("프로젝트 주소 (예: my-landing)") as HTMLInputElement;
|
||||
|
||||
fireEvent.change(titleInput, { target: { value: "충돌 테스트 프로젝트" } });
|
||||
fireEvent.change(slugInput, { target: { value: "conflict-slug" } });
|
||||
|
||||
rerender(<EditorPage />);
|
||||
|
||||
const saveButton = screen.getByText("저장 (로컬 + 서버)");
|
||||
fireEvent.click(saveButton);
|
||||
|
||||
const message = await screen.findByText("이미 다른 사용자가 사용 중인 프로젝트 주소입니다.");
|
||||
expect(message).toBeTruthy();
|
||||
});
|
||||
|
||||
it("로컬스토리지에 프로젝트가 있으면 서버를 호출하지 않고 로컬 데이터로 불러와야 한다", async () => {
|
||||
const slug = "local-test-slug";
|
||||
const localKey = `pb:project:${slug}`;
|
||||
@@ -210,7 +276,11 @@ describe("EditorPage - 프로젝트 저장/불러오기", () => {
|
||||
});
|
||||
|
||||
expect(mockState.replaceBlocks).toHaveBeenCalledWith(savedBlocks as any);
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
|
||||
const serverCalls = fetchMock.mock.calls.filter(
|
||||
([url]: any[]) => typeof url === "string" && url.startsWith("/api/projects"),
|
||||
);
|
||||
expect(serverCalls.length).toBe(0);
|
||||
|
||||
const message = await screen.findByText(`로컬에서 프로젝트를 불러왔습니다: ${slug}`);
|
||||
expect(message).toBeTruthy();
|
||||
@@ -253,10 +323,18 @@ describe("EditorPage - 프로젝트 저장/불러오기", () => {
|
||||
fireEvent.click(loadButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
fetchMock.mock.calls.some(
|
||||
([url]: any[]) => url === `/api/projects/${slug}`,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
const [url] = fetchMock.mock.calls[0] as any;
|
||||
const serverCall = fetchMock.mock.calls.find(
|
||||
([url]: any[]) => url === `/api/projects/${slug}`,
|
||||
) as any;
|
||||
|
||||
const [url] = serverCall;
|
||||
expect(url).toBe(`/api/projects/${slug}`);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -293,10 +371,18 @@ describe("EditorPage - 프로젝트 저장/불러오기", () => {
|
||||
fireEvent.click(deleteMenuItem);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
fetchMock.mock.calls.some(
|
||||
([url, options]: any[]) => url === `/api/projects/${slug}` && options?.method === "DELETE",
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
const [url, options] = fetchMock.mock.calls[0] as any;
|
||||
const deleteCall = fetchMock.mock.calls.find(
|
||||
([url, options]: any[]) => url === `/api/projects/${slug}` && options?.method === "DELETE",
|
||||
) as any;
|
||||
|
||||
const [url, options] = deleteCall;
|
||||
expect(url).toBe(`/api/projects/${slug}`);
|
||||
expect(options.method).toBe("DELETE");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user