test(editor): cover auto-layout hover targets
This commit is contained in:
parent
00110c6c65
commit
d2c3b1bce8
|
|
@ -15,7 +15,7 @@
|
|||
"lint": "bun run lint:structure && oxlint -c oxlint.json --type-aware --type-check src/ packages/core/src/ packages/vue/src/ packages/cli/src/ packages/mcp/src/",
|
||||
"lint:structure": "oxlint -c oxlint.json vite.config.ts vite/ src/ packages/core/src/ packages/vue/src/ packages/cli/src/ packages/mcp/src/ tests/ scripts/",
|
||||
"format": "oxfmt --write .oxfmtrc.json vite.config.ts vite/ src/ packages/core/src/ packages/cli/src/ packages/mcp/src/ packages/vue/src/ tests scripts/",
|
||||
"check": "bun run lint && tsgo --noEmit && bun run check:vue && bun run check:i18n",
|
||||
"check": "bun run lint && tsgo --noEmit && bun run check:vue && bun run check:i18n && bun run check:arch",
|
||||
"check:i18n": "bun scripts/check-locales.ts",
|
||||
"check:arch": "steiger .",
|
||||
"check:vue": "vue-tsc --noEmit -p tsconfig.json && vue-tsc --noEmit -p packages/vue/tsconfig.json",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref, watch } from 'vue'
|
||||
import { computed, nextTick, ref, watch, type Component } from 'vue'
|
||||
import {
|
||||
AUTO_LAYOUT_PADDING_EDITOR_OFFSET_X,
|
||||
AUTO_LAYOUT_PADDING_EDITOR_OFFSET_Y
|
||||
|
|
@ -26,6 +26,10 @@ import { useEditorStore } from '@/app/editor/active-store'
|
|||
import { useCanvasCollaborationAwareness } from '@/app/editor/canvas/collaboration-awareness'
|
||||
import { createCanvasContextSelection } from '@/app/editor/canvas/context-selection'
|
||||
import { fadeOutGlobalLoader } from '@/app/editor/canvas/loader-overlay'
|
||||
import IconLucidePanelBottom from '~icons/lucide/panel-bottom'
|
||||
import IconLucidePanelLeft from '~icons/lucide/panel-left'
|
||||
import IconLucidePanelRight from '~icons/lucide/panel-right'
|
||||
import IconLucidePanelTop from '~icons/lucide/panel-top'
|
||||
import CanvasMenu from './CanvasMenu.vue'
|
||||
import ScrubInput from './ScrubInput.vue'
|
||||
|
||||
|
|
@ -67,6 +71,13 @@ const {
|
|||
useTextEdit(canvasRef, store)
|
||||
const { isDraggingOver } = useCanvasDrop(canvasRef, store)
|
||||
|
||||
const paddingSideIcons = {
|
||||
top: IconLucidePanelTop,
|
||||
right: IconLucidePanelRight,
|
||||
bottom: IconLucidePanelBottom,
|
||||
left: IconLucidePanelLeft
|
||||
} satisfies Record<'top' | 'right' | 'bottom' | 'left', Component>
|
||||
|
||||
const paddingEditorRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const paddingEditorAnchor = computed(() => {
|
||||
|
|
@ -83,6 +94,10 @@ const paddingEditorAnchor = computed(() => {
|
|||
return { x: abs.x + node.width - node.paddingRight / 2, y: abs.y + node.height / 2 }
|
||||
})
|
||||
const paddingEditorReference = useCanvasVirtualReference(canvasRef, store, paddingEditorAnchor)
|
||||
const paddingEditorIcon = computed(() => {
|
||||
const edit = autoLayoutPaddingEdit.value
|
||||
return edit ? paddingSideIcons[edit.side] : IconLucidePanelTop
|
||||
})
|
||||
|
||||
watch(autoLayoutPaddingEdit, async (edit) => {
|
||||
if (!edit) return
|
||||
|
|
@ -146,11 +161,14 @@ const cursor = computed(() => toolCursor(store.state.activeTool, cursorOverride.
|
|||
:model-value="autoLayoutPaddingEdit.value"
|
||||
:min="0"
|
||||
:step="1"
|
||||
icon="↔"
|
||||
data-test-id="auto-layout-padding-input"
|
||||
@update:model-value="updateAutoLayoutPaddingEdit"
|
||||
@commit="(value: number) => commitAutoLayoutPaddingEdit(value)"
|
||||
/>
|
||||
>
|
||||
<template #icon>
|
||||
<component :is="paddingEditorIcon" class="size-3.5" />
|
||||
</template>
|
||||
</ScrubInput>
|
||||
</PopoverContent>
|
||||
</PopoverPortal>
|
||||
</PopoverRoot>
|
||||
|
|
|
|||
112
tests/engine/vue/input/auto-layout-hover.test.ts
Normal file
112
tests/engine/vue/input/auto-layout-hover.test.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import { describe, expect, test } from 'bun:test'
|
||||
|
||||
import { resolveAutoLayoutHover } from '#vue/shared/input/auto-layout-hover'
|
||||
import type { Editor } from '@open-pencil/core/editor'
|
||||
import type { SceneNode } from '@open-pencil/core/scene-graph'
|
||||
|
||||
function frame(overrides: Partial<SceneNode> = {}): SceneNode {
|
||||
return {
|
||||
id: 'frame',
|
||||
type: 'FRAME',
|
||||
name: 'Frame',
|
||||
parentId: 'page',
|
||||
childIds: ['title', 'body', 'bar'],
|
||||
visible: true,
|
||||
locked: false,
|
||||
x: 100,
|
||||
y: 100,
|
||||
width: 280,
|
||||
height: 160,
|
||||
rotation: 0,
|
||||
layoutMode: 'VERTICAL',
|
||||
layoutPositioning: 'AUTO',
|
||||
itemSpacing: 8,
|
||||
paddingTop: 20,
|
||||
paddingRight: 20,
|
||||
paddingBottom: 20,
|
||||
paddingLeft: 20,
|
||||
...overrides
|
||||
} as SceneNode
|
||||
}
|
||||
|
||||
function child(id: string, y: number, height: number): SceneNode {
|
||||
return {
|
||||
id,
|
||||
type: id === 'bar' ? 'RECTANGLE' : 'TEXT',
|
||||
name: id,
|
||||
parentId: 'frame',
|
||||
childIds: [],
|
||||
visible: true,
|
||||
locked: false,
|
||||
x: 20,
|
||||
y,
|
||||
width: id === 'bar' ? 180 : 240,
|
||||
height,
|
||||
rotation: 0,
|
||||
layoutMode: 'NONE',
|
||||
layoutPositioning: 'AUTO'
|
||||
} as SceneNode
|
||||
}
|
||||
|
||||
function editor() {
|
||||
const nodes = new Map<string, SceneNode>([
|
||||
['frame', frame()],
|
||||
['title', child('title', 20, 32)],
|
||||
['body', child('body', 60, 48)],
|
||||
['bar', child('bar', 116, 12)]
|
||||
])
|
||||
|
||||
return {
|
||||
state: {
|
||||
selectedIds: new Set(['frame'])
|
||||
},
|
||||
graph: {
|
||||
getNode: (id: string) => nodes.get(id) ?? null,
|
||||
getAbsolutePosition: (id: string) => {
|
||||
const node = nodes.get(id)
|
||||
if (!node) return { x: 0, y: 0 }
|
||||
if (node.parentId === 'frame') return { x: 100 + node.x, y: 100 + node.y }
|
||||
return { x: node.x, y: node.y }
|
||||
}
|
||||
}
|
||||
} as Editor
|
||||
}
|
||||
|
||||
describe('auto-layout hover resolver', () => {
|
||||
test('returns null outside the selected auto-layout frame', () => {
|
||||
expect(resolveAutoLayoutHover(90, 90, editor())).toBeNull()
|
||||
})
|
||||
|
||||
test('detects padding regions and padding value handles', () => {
|
||||
expect(resolveAutoLayoutHover(240, 105, editor())).toMatchObject({
|
||||
kind: 'padding-value',
|
||||
side: 'top'
|
||||
})
|
||||
expect(resolveAutoLayoutHover(375, 130, editor())).toMatchObject({
|
||||
kind: 'padding',
|
||||
side: 'right'
|
||||
})
|
||||
})
|
||||
|
||||
test('detects shared spacing regions and value handles', () => {
|
||||
expect(resolveAutoLayoutHover(240, 156, editor())).toMatchObject({
|
||||
kind: 'spacing-value',
|
||||
index: 0
|
||||
})
|
||||
expect(resolveAutoLayoutHover(130, 212, editor())).toMatchObject({
|
||||
kind: 'spacing',
|
||||
index: 1
|
||||
})
|
||||
})
|
||||
|
||||
test('detects child hover inside selected auto-layout frame', () => {
|
||||
expect(resolveAutoLayoutHover(130, 180, editor())).toMatchObject({
|
||||
kind: 'children',
|
||||
index: 1
|
||||
})
|
||||
})
|
||||
|
||||
test('falls back to frame hover inside empty selected areas', () => {
|
||||
expect(resolveAutoLayoutHover(240, 235, editor())).toMatchObject({ kind: 'frame' })
|
||||
})
|
||||
})
|
||||
Loading…
Reference in a new issue