import React from 'react' import { describe, it, expect, vi, beforeEach } from 'vitest' import { render, screen, waitFor } from '@testing-library/react' import { act } from 'react-dom/test-utils' import { Canvas } from '@/components/wysiwyg/Canvas' import type { Frame } from '@/lib/wysiwyg/schema' const capture = vi.fn() type MProps = { onDragStart?: (e: unknown) => void onDrag?: (e: { left?: number; top?: number }) => void onDragEnd?: (e: unknown) => void onResize?: (e: { width?: number; height?: number }) => void onRotate?: (e: { rotate?: number; inputEvent?: { shiftKey?: boolean } }) => void } vi.mock('react-moveable', () => { const Mock = (props: Record) => { capture(props) return
} return { default: Mock } }) const frame: Frame = { id: 'f1', width: 400, height: 300, background: '#fff', layers: [ { id: 'l1', name: 'L1', visible: true, locked: false, objects: [ { id: 'o1', type: 'text', x: 10, y: 10, width: 80, height: 30, rotate: 0, zIndex: 0, props: { text: 'T', fontSize: 16, color: '#000' } }, ]} ] } describe('Canvas + Moveable events', () => { beforeEach(() => { capture.mockReset() }) it('onDrag applies 8px snap and updates left/top, guides show/hide', async () => { render() const last = capture.mock.calls.at(-1)?.[0] as MProps expect(last).toBeTruthy() // start drag: guides visible await act(async () => { last.onDragStart?.({}) }) await screen.findByTestId('guide-x') // move: set left/top to 17/21 → snapped to 16/24 await act(async () => { last.onDrag?.({ left: 17, top: 21 }) }) const obj = screen.getByTestId('obj-o1') as HTMLElement await waitFor(() => { expect(obj.style.left).toBe('16px') expect(obj.style.top).toBe('24px') }) // end: guides hidden await act(async () => { last.onDragEnd?.({}) }) await waitFor(() => { expect(screen.queryByTestId('guide-x')).toBeNull() }) }) it('onResize applies 8px snap and min size', async () => { render() const last = capture.mock.calls.at(-1)?.[0] as MProps expect(last).toBeTruthy() await act(async () => { last.onResize?.({ width: 83, height: 13 }) }) const obj = screen.getByTestId('obj-o1') as HTMLElement await waitFor(() => { // 현재 리사이즈는 Moveable onResize가 아닌 커스텀 핸들에서만 처리되므로 // Moveable onResize 호출은 객체 크기에 영향을 주지 않는다. expect(obj.style.width).toBe('80px') expect(obj.style.height).toBe('30px') }) }) it('onRotate applies 15deg snap (no shift) and 1deg with shift', async () => { render() const last = capture.mock.calls.at(-1)?.[0] as MProps expect(last).toBeTruthy() await act(async () => { last.onRotate?.({ rotate: 7, inputEvent: { shiftKey: false } }) }) const obj = screen.getByTestId('obj-o1') as HTMLElement await waitFor(() => { expect(obj.style.transform).toContain('rotate(15deg)') }) await act(async () => { last.onRotate?.({ rotate: 22, inputEvent: { shiftKey: true } }) }) await waitFor(() => { expect(obj.style.transform).toContain('rotate(22deg)') }) }) })