럭키시트 로드 가능, 옵션이 안불러짐
This commit is contained in:
402
src/components/sheet/__tests__/SheetViewer.test.tsx
Normal file
402
src/components/sheet/__tests__/SheetViewer.test.tsx
Normal file
@@ -0,0 +1,402 @@
|
||||
import React from "react";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom";
|
||||
import { vi } from "vitest";
|
||||
import { SheetViewer } from "../SheetViewer";
|
||||
import { useAppStore } from "../../../stores/useAppStore";
|
||||
import type { SheetData } from "../../../types/sheet";
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("../../../stores/useAppStore");
|
||||
|
||||
// Luckysheet 모킹
|
||||
const mockLuckysheet = {
|
||||
create: vi.fn(),
|
||||
destroy: vi.fn(),
|
||||
resize: vi.fn(),
|
||||
getSheet: vi.fn(),
|
||||
getAllSheets: vi.fn(),
|
||||
setActiveSheet: vi.fn(),
|
||||
};
|
||||
|
||||
// Window.luckysheet 모킹
|
||||
Object.defineProperty(window, "luckysheet", {
|
||||
value: mockLuckysheet,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
// useAppStore 모킹 타입
|
||||
const mockUseAppStore = vi.mocked(useAppStore);
|
||||
|
||||
// 기본 스토어 상태
|
||||
const defaultStoreState = {
|
||||
sheets: [],
|
||||
activeSheetId: null,
|
||||
currentFile: null,
|
||||
setSelectedRange: vi.fn(),
|
||||
isLoading: false,
|
||||
error: null,
|
||||
setLoading: vi.fn(),
|
||||
setError: vi.fn(),
|
||||
uploadFile: vi.fn(),
|
||||
clearFileUploadErrors: vi.fn(),
|
||||
resetApp: vi.fn(),
|
||||
};
|
||||
|
||||
// 테스트용 시트 데이터
|
||||
const mockSheetData: SheetData[] = [
|
||||
{
|
||||
id: "sheet_0",
|
||||
name: "Sheet1",
|
||||
data: [
|
||||
["A1", "B1", "C1"],
|
||||
["A2", "B2", "C2"],
|
||||
],
|
||||
config: {
|
||||
container: "luckysheet_0",
|
||||
title: "Sheet1",
|
||||
lang: "ko",
|
||||
data: [
|
||||
{
|
||||
name: "Sheet1",
|
||||
index: "0",
|
||||
celldata: [
|
||||
{
|
||||
r: 0,
|
||||
c: 0,
|
||||
v: { v: "A1", m: "A1", ct: { fa: "General", t: "g" } },
|
||||
},
|
||||
{
|
||||
r: 0,
|
||||
c: 1,
|
||||
v: { v: "B1", m: "B1", ct: { fa: "General", t: "g" } },
|
||||
},
|
||||
],
|
||||
status: 1,
|
||||
order: 0,
|
||||
row: 2,
|
||||
column: 3,
|
||||
},
|
||||
],
|
||||
options: {
|
||||
showtoolbar: true,
|
||||
showinfobar: false,
|
||||
showsheetbar: true,
|
||||
showstatisticBar: false,
|
||||
allowCopy: true,
|
||||
allowEdit: true,
|
||||
enableAddRow: true,
|
||||
enableAddCol: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe("SheetViewer", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockUseAppStore.mockReturnValue(defaultStoreState);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// DOM 정리
|
||||
document.head.innerHTML = "";
|
||||
});
|
||||
|
||||
describe("초기 렌더링", () => {
|
||||
it("시트 데이터가 없을 때 적절한 메시지를 표시한다", () => {
|
||||
mockUseAppStore.mockReturnValue({
|
||||
...defaultStoreState,
|
||||
sheets: [],
|
||||
});
|
||||
|
||||
render(<SheetViewer />);
|
||||
|
||||
expect(screen.getByText("표시할 시트가 없습니다")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText("Excel 파일을 업로드해주세요."),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("시트 데이터가 있을 때 로딩 상태를 표시한다", () => {
|
||||
mockUseAppStore.mockReturnValue({
|
||||
...defaultStoreState,
|
||||
sheets: mockSheetData,
|
||||
currentFile: { name: "test.xlsx", size: 1000, uploadedAt: new Date() },
|
||||
});
|
||||
|
||||
render(<SheetViewer />);
|
||||
|
||||
expect(screen.getByText("시트 로딩 중...")).toBeInTheDocument();
|
||||
expect(screen.getByText("잠시만 기다려주세요.")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("Luckysheet 컨테이너가 올바르게 렌더링된다", () => {
|
||||
mockUseAppStore.mockReturnValue({
|
||||
...defaultStoreState,
|
||||
sheets: mockSheetData,
|
||||
currentFile: { name: "test.xlsx", size: 1000, uploadedAt: new Date() },
|
||||
});
|
||||
|
||||
render(<SheetViewer />);
|
||||
|
||||
const container = document.getElementById("luckysheet-container");
|
||||
expect(container).toBeInTheDocument();
|
||||
expect(container).toHaveClass("w-full", "h-full");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Luckysheet 초기화", () => {
|
||||
it("시트 데이터가 변경되면 Luckysheet를 초기화한다", async () => {
|
||||
mockUseAppStore.mockReturnValue({
|
||||
...defaultStoreState,
|
||||
sheets: mockSheetData,
|
||||
currentFile: { name: "test.xlsx", size: 1000, uploadedAt: new Date() },
|
||||
});
|
||||
|
||||
render(<SheetViewer />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockLuckysheet.create).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// create 호출 시 전달된 설정 확인
|
||||
const createCall = mockLuckysheet.create.mock.calls[0];
|
||||
expect(createCall).toBeDefined();
|
||||
|
||||
const config = createCall[0];
|
||||
expect(config.container).toBe("luckysheet-container");
|
||||
expect(config.title).toBe("test.xlsx");
|
||||
expect(config.lang).toBe("ko");
|
||||
expect(config.data).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("기존 Luckysheet 인스턴스가 있으면 제거한다", async () => {
|
||||
mockUseAppStore.mockReturnValue({
|
||||
...defaultStoreState,
|
||||
sheets: mockSheetData,
|
||||
currentFile: { name: "test.xlsx", size: 1000, uploadedAt: new Date() },
|
||||
});
|
||||
|
||||
const { rerender } = render(<SheetViewer />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockLuckysheet.create).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// 시트 데이터 변경
|
||||
const newSheetData: SheetData[] = [
|
||||
{
|
||||
...mockSheetData[0],
|
||||
name: "NewSheet",
|
||||
},
|
||||
];
|
||||
|
||||
mockUseAppStore.mockReturnValue({
|
||||
...defaultStoreState,
|
||||
sheets: newSheetData,
|
||||
currentFile: { name: "new.xlsx", size: 1000, uploadedAt: new Date() },
|
||||
});
|
||||
|
||||
rerender(<SheetViewer />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockLuckysheet.destroy).toHaveBeenCalled();
|
||||
expect(mockLuckysheet.create).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("에러 처리", () => {
|
||||
it("Luckysheet 초기화 실패 시 에러 메시지를 표시한다", async () => {
|
||||
mockUseAppStore.mockReturnValue({
|
||||
...defaultStoreState,
|
||||
sheets: mockSheetData,
|
||||
currentFile: { name: "test.xlsx", size: 1000, uploadedAt: new Date() },
|
||||
});
|
||||
|
||||
// Luckysheet.create에서 에러 발생 시뮬레이션
|
||||
mockLuckysheet.create.mockImplementation(() => {
|
||||
throw new Error("Luckysheet 초기화 실패");
|
||||
});
|
||||
|
||||
render(<SheetViewer />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("시트 로드 오류")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/시트 초기화에 실패했습니다/),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// 다시 시도 버튼 확인
|
||||
const retryButton = screen.getByRole("button", { name: "다시 시도" });
|
||||
expect(retryButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("다시 시도 버튼을 클릭하면 초기화를 재시도한다", async () => {
|
||||
mockUseAppStore.mockReturnValue({
|
||||
...defaultStoreState,
|
||||
sheets: mockSheetData,
|
||||
currentFile: { name: "test.xlsx", size: 1000, uploadedAt: new Date() },
|
||||
});
|
||||
|
||||
// 첫 번째 시도에서 실패
|
||||
mockLuckysheet.create.mockImplementationOnce(() => {
|
||||
throw new Error("첫 번째 실패");
|
||||
});
|
||||
|
||||
render(<SheetViewer />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("시트 로드 오류")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// 두 번째 시도에서 성공하도록 설정
|
||||
mockLuckysheet.create.mockImplementationOnce(() => {
|
||||
// 성공
|
||||
});
|
||||
|
||||
const retryButton = screen.getByRole("button", { name: "다시 시도" });
|
||||
retryButton.click();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockLuckysheet.create).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("이벤트 핸들링", () => {
|
||||
it("셀 클릭 시 선택된 범위를 스토어에 저장한다", async () => {
|
||||
const mockSetSelectedRange = vi.fn();
|
||||
mockUseAppStore.mockReturnValue({
|
||||
...defaultStoreState,
|
||||
sheets: mockSheetData,
|
||||
currentFile: { name: "test.xlsx", size: 1000, uploadedAt: new Date() },
|
||||
setSelectedRange: mockSetSelectedRange,
|
||||
});
|
||||
|
||||
render(<SheetViewer />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockLuckysheet.create).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// create 호출 시 전달된 hook 확인
|
||||
const createCall = mockLuckysheet.create.mock.calls[0];
|
||||
const config = createCall[0];
|
||||
|
||||
// cellClick 핸들러 시뮬레이션
|
||||
const cellClickHandler = config.hook.cellClick;
|
||||
expect(cellClickHandler).toBeDefined();
|
||||
|
||||
const mockCell = {};
|
||||
const mockPosition = { r: 1, c: 2 };
|
||||
const mockSheetFile = { index: "0" };
|
||||
|
||||
cellClickHandler(mockCell, mockPosition, mockSheetFile);
|
||||
|
||||
expect(mockSetSelectedRange).toHaveBeenCalledWith({
|
||||
range: {
|
||||
startRow: 1,
|
||||
startCol: 2,
|
||||
endRow: 1,
|
||||
endCol: 2,
|
||||
},
|
||||
sheetId: "0",
|
||||
});
|
||||
});
|
||||
|
||||
it("시트 활성화 시 활성 시트 ID를 업데이트한다", async () => {
|
||||
mockUseAppStore.mockReturnValue({
|
||||
...defaultStoreState,
|
||||
sheets: mockSheetData,
|
||||
currentFile: { name: "test.xlsx", size: 1000, uploadedAt: new Date() },
|
||||
});
|
||||
|
||||
// setActiveSheetId를 spy로 설정
|
||||
const setActiveSheetIdSpy = vi.fn();
|
||||
useAppStore.getState = vi.fn().mockReturnValue({
|
||||
setActiveSheetId: setActiveSheetIdSpy,
|
||||
});
|
||||
|
||||
render(<SheetViewer />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockLuckysheet.create).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// create 호출 시 전달된 hook 확인
|
||||
const createCall = mockLuckysheet.create.mock.calls[0];
|
||||
const config = createCall[0];
|
||||
|
||||
// sheetActivate 핸들러 시뮬레이션
|
||||
const sheetActivateHandler = config.hook.sheetActivate;
|
||||
expect(sheetActivateHandler).toBeDefined();
|
||||
|
||||
sheetActivateHandler(0, false, false);
|
||||
|
||||
expect(setActiveSheetIdSpy).toHaveBeenCalledWith("sheet_0");
|
||||
});
|
||||
});
|
||||
|
||||
describe("컴포넌트 생명주기", () => {
|
||||
it("컴포넌트 언마운트 시 Luckysheet를 정리한다", async () => {
|
||||
mockUseAppStore.mockReturnValue({
|
||||
...defaultStoreState,
|
||||
sheets: mockSheetData,
|
||||
currentFile: { name: "test.xlsx", size: 1000, uploadedAt: new Date() },
|
||||
});
|
||||
|
||||
const { unmount } = render(<SheetViewer />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockLuckysheet.create).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
unmount();
|
||||
|
||||
expect(mockLuckysheet.destroy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("윈도우 리사이즈 시 Luckysheet 리사이즈를 호출한다", async () => {
|
||||
mockUseAppStore.mockReturnValue({
|
||||
...defaultStoreState,
|
||||
sheets: mockSheetData,
|
||||
currentFile: { name: "test.xlsx", size: 1000, uploadedAt: new Date() },
|
||||
});
|
||||
|
||||
render(<SheetViewer />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockLuckysheet.create).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// 윈도우 리사이즈 이벤트 시뮬레이션
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
|
||||
expect(mockLuckysheet.resize).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("개발 모드 정보", () => {
|
||||
it("개발 모드에서 시트 정보를 표시한다", () => {
|
||||
const originalEnv = process.env.NODE_ENV;
|
||||
process.env.NODE_ENV = "development";
|
||||
|
||||
mockUseAppStore.mockReturnValue({
|
||||
...defaultStoreState,
|
||||
sheets: mockSheetData,
|
||||
activeSheetId: "sheet_0",
|
||||
currentFile: { name: "test.xlsx", size: 1000, uploadedAt: new Date() },
|
||||
});
|
||||
|
||||
render(<SheetViewer />);
|
||||
|
||||
expect(screen.getByText("시트 개수: 1")).toBeInTheDocument();
|
||||
expect(screen.getByText("활성 시트: sheet_0")).toBeInTheDocument();
|
||||
|
||||
process.env.NODE_ENV = originalEnv;
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user