Files
sheeteasyAI/src/components/sheet/__tests__/SheetViewer.test.tsx
2025-06-24 14:15:09 +09:00

403 lines
11 KiB
TypeScript

// 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;
});
});
});