403 lines
11 KiB
TypeScript
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;
|
|
});
|
|
});
|
|
});
|