테스트/린트 안정화: 버튼 쿼리 모호성 제거, 훅 deps 보강, vi.spyOn 모킹, 멀티선택 Moveable 간섭 방지 재확인, 스냅 우선순위 회귀 그린
Auto PR / open-pr (push) Successful in 21s
Auto Label PR / add-automerge-label (pull_request) Successful in 6s
CI / test (pull_request) Successful in 1m49s

This commit is contained in:
2025-11-17 17:33:39 +09:00
parent 1c607f6331
commit 5bf78b3e01
29 changed files with 1861 additions and 80 deletions
@@ -0,0 +1,156 @@
import { describe, it, expect } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import { WysiwygBuilderPage } from '@/components/wysiwyg/WysiwygBuilderPage'
function getBox() {
const el = screen.getByTestId(/obj-text-/) as HTMLElement
const style = el.style
const left = parseInt(style.left || '0', 10)
const top = parseInt(style.top || '0', 10)
const width = parseInt(style.width || '0', 10)
const height = parseInt(style.height || '0', 10)
return { left, top, width, height }
}
function snap8(v: number) {
return Math.round(v / 8) * 8
}
describe('WYSIWYG Builder - Resize directional handles', () => {
it('left handle resizes by moving left edge and shifts x', async () => {
render(<WysiwygBuilderPage />)
fireEvent.click(screen.getByRole('button', { name: /^Add Text$/i }))
const obj = await screen.findByTestId(/obj-text-/)
// select
fireEvent.mouseDown(obj, { clientX: 80, clientY: 80 })
// end any potential drag
fireEvent.mouseUp(screen.getByTestId('wysiwyg-canvas'))
const before = getBox()
const hLeft = await screen.findByTestId(/resize-handle-w-/)
// drag left handle to the left by 16px (two 8px snaps)
fireEvent.mouseDown(hLeft, { clientX: before.left, clientY: before.top + 5 })
fireEvent.mouseMove(screen.getByTestId('wysiwyg-canvas'), { clientX: before.left - 16, clientY: before.top + 5 })
fireEvent.mouseUp(screen.getByTestId('wysiwyg-canvas'))
const after = getBox()
const expectedLeft = Math.min(snap8(before.left - 16), before.left + before.width - 8)
const expectedWidth = (before.left + before.width) - expectedLeft
expect(after.left).toBe(expectedLeft)
expect(after.width).toBe(expectedWidth)
// orthogonal axis may adjust minimally due to snapping/rounding; primary check is x/width
})
it('corner NW handle moves left/top and expands width/height', async () => {
render(<WysiwygBuilderPage />)
fireEvent.click(screen.getByRole('button', { name: /^Add Text$/i }))
const obj = await screen.findByTestId(/obj-text-/)
fireEvent.mouseDown(obj, { clientX: 80, clientY: 80 })
fireEvent.mouseUp(screen.getByTestId('wysiwyg-canvas'))
const before = getBox()
const hNW = await screen.findByTestId(/resize-handle-nw-/)
fireEvent.mouseDown(hNW, { clientX: before.left, clientY: before.top })
fireEvent.mouseMove(screen.getByTestId('wysiwyg-canvas'), { clientX: before.left - 16, clientY: before.top - 16 })
fireEvent.mouseUp(screen.getByTestId('wysiwyg-canvas'))
const after = getBox()
const expLeft = Math.min(snap8(before.left - 16), before.left + before.width - 8)
const expTop = Math.min(snap8(before.top - 16), before.top + before.height - 8)
expect(after.left).toBe(expLeft)
expect(after.top).toBe(expTop)
expect(after.width).toBe((before.left + before.width) - expLeft)
expect(after.height).toBe((before.top + before.height) - expTop)
})
it('corner SW handle moves left and expands height with bottom drag', async () => {
render(<WysiwygBuilderPage />)
fireEvent.click(screen.getByRole('button', { name: /^Add Text$/i }))
const obj = await screen.findByTestId(/obj-text-/)
fireEvent.mouseDown(obj, { clientX: 80, clientY: 80 })
fireEvent.mouseUp(screen.getByTestId('wysiwyg-canvas'))
const before = getBox()
const hSW = await screen.findByTestId(/resize-handle-sw-/)
fireEvent.mouseDown(hSW, { clientX: before.left, clientY: before.top + before.height })
fireEvent.mouseMove(screen.getByTestId('wysiwyg-canvas'), { clientX: before.left - 16, clientY: before.top + before.height + 16 })
fireEvent.mouseUp(screen.getByTestId('wysiwyg-canvas'))
const after = getBox()
const expLeft2 = Math.min(snap8(before.left - 16), before.left + before.width - 8)
expect(after.left).toBe(expLeft2)
expect(after.width).toBe((before.left + before.width) - expLeft2)
// bottom 증가, top은 유지
expect(after.top).toBe(before.top)
expect(after.height).toBe(snap8(before.height + 16))
})
it('top handle resizes by moving top edge and shifts y', async () => {
render(<WysiwygBuilderPage />)
fireEvent.click(screen.getByRole('button', { name: /^Add Text$/i }))
const obj = await screen.findByTestId(/obj-text-/)
fireEvent.mouseDown(obj, { clientX: 80, clientY: 80 })
fireEvent.mouseUp(screen.getByTestId('wysiwyg-canvas'))
const before = getBox()
const hTop = await screen.findByTestId(/resize-handle-n-/)
fireEvent.mouseDown(hTop, { clientX: before.left + 5, clientY: before.top })
fireEvent.mouseMove(screen.getByTestId('wysiwyg-canvas'), { clientX: before.left + 5, clientY: before.top - 16 })
fireEvent.mouseUp(screen.getByTestId('wysiwyg-canvas'))
const after = getBox()
const expectedTop = Math.min(snap8(before.top - 16), before.top + before.height - 8)
const expectedHeight = (before.top + before.height) - expectedTop
expect(after.top).toBe(expectedTop)
expect(after.height).toBe(expectedHeight)
// orthogonal axis may adjust minimally due to snapping; primary check is y/height
})
it('right and bottom handles increase width/height without shifting origin', async () => {
render(<WysiwygBuilderPage />)
fireEvent.click(screen.getByRole('button', { name: /^Add Text$/i }))
const obj = await screen.findByTestId(/obj-text-/)
fireEvent.mouseDown(obj, { clientX: 80, clientY: 80 })
const before = getBox()
const hRight = await screen.findByTestId(/resize-handle-e-/)
fireEvent.mouseDown(hRight, { clientX: before.left + before.width, clientY: before.top + 5 })
fireEvent.mouseMove(screen.getByTestId('wysiwyg-canvas'), { clientX: before.left + before.width + 16, clientY: before.top + 5 })
fireEvent.mouseUp(screen.getByTestId('wysiwyg-canvas'))
const mid = getBox()
// primary check: width increased by snapped delta
expect(mid.width).toBe(snap8(before.width + 16))
const hBottom = await screen.findByTestId(/resize-handle-s-/)
fireEvent.mouseDown(hBottom, { clientX: mid.left + 5, clientY: mid.top + mid.height })
fireEvent.mouseMove(screen.getByTestId('wysiwyg-canvas'), { clientX: mid.left + 5, clientY: mid.top + mid.height + 16 })
fireEvent.mouseUp(screen.getByTestId('wysiwyg-canvas'))
const after = getBox()
// origin should remain same on y for bottom handle
expect(after.top).toBe(mid.top)
expect(after.height).toBe(snap8(mid.height + 16))
})
it('corner NE handle adjusts width and height with appropriate origin shift on Y', async () => {
render(<WysiwygBuilderPage />)
fireEvent.click(screen.getByRole('button', { name: /^Add Text$/i }))
const obj = await screen.findByTestId(/obj-text-/)
fireEvent.mouseDown(obj, { clientX: 80, clientY: 80 })
const before = getBox()
const hNE = await screen.findByTestId(/resize-handle-ne-/)
fireEvent.mouseDown(hNE, { clientX: before.left + before.width, clientY: before.top })
fireEvent.mouseMove(screen.getByTestId('wysiwyg-canvas'), { clientX: before.left + before.width + 16, clientY: before.top - 16 })
fireEvent.mouseUp(screen.getByTestId('wysiwyg-canvas'))
const after = getBox()
// NE: primary checks are top shifted up and width increased; left origin may adjust under snapping
const expectedTop2 = Math.min(snap8(before.top - 16), before.top + before.height - 8)
expect(after.top).toBe(expectedTop2)
expect(after.width).toBe(snap8(before.width + 16))
expect(after.height).toBe((before.top + before.height) - expectedTop2)
})
})