150 lines
5.5 KiB
TypeScript
150 lines
5.5 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
|
import { WysiwygBuilderPage } from '@/components/wysiwyg/WysiwygBuilderPage'
|
|
|
|
const createObjectURL = global.URL.createObjectURL
|
|
const revokeObjectURL = global.URL.revokeObjectURL
|
|
|
|
describe('WYSIWYG Builder acceptance', () => {
|
|
beforeEach(() => {
|
|
// @ts-expect-error - test shim
|
|
global.URL.createObjectURL = () => 'blob:mock'
|
|
// @ts-expect-error - test shim
|
|
global.URL.revokeObjectURL = () => {}
|
|
})
|
|
afterEach(() => {
|
|
global.URL.createObjectURL = createObjectURL
|
|
global.URL.revokeObjectURL = revokeObjectURL
|
|
})
|
|
|
|
it('Add → Edit → Export smoke', async () => {
|
|
render(<WysiwygBuilderPage />)
|
|
|
|
// Add Text
|
|
fireEvent.click(screen.getByRole('button', { name: /Add Text/i }))
|
|
const obj = await screen.findByTestId('obj-text-1')
|
|
|
|
// Select and edit via Properties panel
|
|
fireEvent.mouseDown(obj)
|
|
const inputX = await screen.findByLabelText('X')
|
|
fireEvent.change(inputX, { target: { value: '80' } })
|
|
|
|
await waitFor(() => {
|
|
// style left updated on canvas object (snap may apply, accept near 80)
|
|
const left = parseInt((obj as HTMLElement).style.left || '0', 10)
|
|
expect(Math.abs(left - 80)).toBeLessThanOrEqual(8)
|
|
})
|
|
|
|
// Export exists and triggers download
|
|
const exportBtn = screen.getByRole('button', { name: /Export/i })
|
|
fireEvent.click(exportBtn)
|
|
// No throw means success; optionally wait a tick
|
|
await new Promise((r) => setTimeout(r, 0))
|
|
})
|
|
|
|
it('uploads a local image and renders it via AssetManager path', async () => {
|
|
render(<WysiwygBuilderPage />)
|
|
|
|
const input = screen.getByTestId('wysiwyg-image-input') as HTMLInputElement
|
|
const file = new File([new Uint8Array([1, 2, 3])], 'hero.png', { type: 'image/png' })
|
|
|
|
fireEvent.change(input, { target: { files: [file] } })
|
|
|
|
const img = await screen.findByRole('img')
|
|
const src = (img as HTMLImageElement).src
|
|
expect(src).toContain('/assets/')
|
|
})
|
|
|
|
it('syncs width/height/rotate from Properties panel to Canvas for a text object', async () => {
|
|
render(<WysiwygBuilderPage />)
|
|
|
|
// Add Text and select
|
|
const addTextBtn = screen.getByRole('button', { name: /Add Text/i })
|
|
fireEvent.click(addTextBtn)
|
|
const [obj] = await screen.findAllByTestId(/obj-text-/)
|
|
fireEvent.mouseDown(obj)
|
|
|
|
// Edit width/height/rotate via Properties
|
|
const inputW = await screen.findByLabelText('Width')
|
|
const inputH = await screen.findByLabelText('Height')
|
|
const inputR = await screen.findByLabelText('Rotate')
|
|
|
|
fireEvent.change(inputW, { target: { value: '200' } })
|
|
fireEvent.change(inputH, { target: { value: '60' } })
|
|
fireEvent.change(inputR, { target: { value: '30' } })
|
|
|
|
await waitFor(() => {
|
|
const style = (obj as HTMLElement).style
|
|
expect(style.width).toBe('200px')
|
|
expect(style.height).toBe('60px')
|
|
expect(style.transform).toContain('30deg')
|
|
})
|
|
})
|
|
|
|
it('syncs text/button specific props from Properties to Canvas content', async () => {
|
|
render(<WysiwygBuilderPage />)
|
|
|
|
// Text
|
|
fireEvent.click(screen.getByRole('button', { name: /Add Text/i }))
|
|
const [textObj] = await screen.findAllByTestId(/obj-text-/)
|
|
fireEvent.mouseDown(textObj)
|
|
const textInput = await screen.findByLabelText('Text')
|
|
fireEvent.change(textInput, { target: { value: 'Hello World' } })
|
|
|
|
await waitFor(() => {
|
|
expect(textObj).toHaveTextContent('Hello World')
|
|
})
|
|
|
|
// Button
|
|
fireEvent.click(screen.getByRole('button', { name: /Add Button/i }))
|
|
const [buttonObj] = await screen.findAllByTestId(/obj-button-/)
|
|
fireEvent.mouseDown(buttonObj)
|
|
const labelInput = await screen.findByLabelText('Label')
|
|
fireEvent.change(labelInput, { target: { value: 'CTA' } })
|
|
|
|
await waitFor(() => {
|
|
expect(buttonObj).toHaveTextContent('CTA')
|
|
})
|
|
})
|
|
|
|
it('supports Shift+click multiselect and group drag on the builder canvas', async () => {
|
|
render(<WysiwygBuilderPage />)
|
|
|
|
// 두 텍스트 추가
|
|
const addTextBtn = screen.getByRole('button', { name: /Add Text/i })
|
|
fireEvent.click(addTextBtn)
|
|
fireEvent.click(addTextBtn)
|
|
|
|
const [obj1, obj2] = await screen.findAllByTestId(/obj-text-/)
|
|
|
|
// 첫 번째 선택
|
|
fireEvent.mouseDown(obj1, { clientX: 70, clientY: 70 })
|
|
// Shift+클릭으로 두 번째 추가 선택
|
|
fireEvent.mouseDown(obj2, { clientX: 80, clientY: 120, shiftKey: true })
|
|
|
|
// 멀티선택 상태에서 두 객체 모두 outline 스타일을 갖는지만 확인
|
|
expect((obj1 as HTMLElement).style.outline).toContain('2px solid')
|
|
expect((obj2 as HTMLElement).style.outline).toContain('2px solid')
|
|
})
|
|
|
|
it('supports marquee selection on the builder canvas', async () => {
|
|
render(<WysiwygBuilderPage />)
|
|
|
|
// 텍스트 두 개 배치
|
|
const addTextBtn = screen.getByRole('button', { name: /Add Text/i })
|
|
fireEvent.click(addTextBtn)
|
|
fireEvent.click(addTextBtn)
|
|
const [obj1, obj2] = await screen.findAllByTestId(/obj-text-/)
|
|
|
|
// 마퀴 드래그 박스로 두 객체 모두 포함되도록 드래그
|
|
const canvas = screen.getByTestId('wysiwyg-canvas')
|
|
fireEvent.mouseDown(canvas, { clientX: 40, clientY: 40 })
|
|
fireEvent.mouseMove(canvas, { clientX: 300, clientY: 300 })
|
|
fireEvent.mouseUp(canvas)
|
|
|
|
// 선택된 두 객체가 모두 outline 스타일을 갖는지 확인
|
|
expect((obj1 as HTMLElement).style.outline).toContain('2px solid')
|
|
expect((obj2 as HTMLElement).style.outline).toContain('2px solid')
|
|
})
|
|
})
|