Files
homelable/frontend/src/utils/__tests__/alignmentSettings.test.ts
T
Pouzor 004a8f19c1 feat(canvas): alignment guides + snap while dragging nodes
Draw.io / Figma style: while dragging a node, show dashed cyan guide
lines when its edges (left / center / right / top / middle / bottom)
align with another node's, and snap the position to the matched line
within a configurable threshold.

- utils/alignment.ts: pure snap math, returns delta + guide segments.
  Same-size boxes show all aligned guides simultaneously.
- canvas/AlignmentGuides.tsx: SVG overlay locked to the React Flow
  viewport (panned/zoomed correctly).
- hooks/useAlignmentGuides.ts: wires onNodeDrag/onNodeDragStop, applies
  snap via setNodes, listens for Alt to temporarily disable.
- utils/alignmentSettings.ts: localStorage-backed prefs (enabled,
  threshold 2-16px) with a tiny CustomEvent pub-sub so the SettingsPanel
  and the drag hook stay in sync without a global store.
- Sidebar settings panel: toggle + threshold slider.

Multi-selection drag uses the union bounding box. Children with
parentId are skipped for v1 to avoid mixing absolute and parent-relative
coordinates. Hold Alt to escape snap.
2026-05-11 00:35:50 +02:00

43 lines
1.5 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from 'vitest'
import {
DEFAULT_ALIGNMENT_SETTINGS,
readAlignmentSettings,
writeAlignmentSettings,
subscribeAlignmentSettings,
} from '../alignmentSettings'
describe('alignmentSettings', () => {
beforeEach(() => {
localStorage.clear()
})
it('returns defaults when nothing stored', () => {
expect(readAlignmentSettings()).toEqual(DEFAULT_ALIGNMENT_SETTINGS)
})
it('roundtrips through localStorage', () => {
writeAlignmentSettings({ enabled: false, threshold: 10 })
expect(readAlignmentSettings()).toEqual({ enabled: false, threshold: 10 })
})
it('falls back to defaults when stored value is corrupted', () => {
localStorage.setItem('homelable.alignmentGuides', '{not json')
expect(readAlignmentSettings()).toEqual(DEFAULT_ALIGNMENT_SETTINGS)
})
it('fills missing fields from defaults', () => {
localStorage.setItem('homelable.alignmentGuides', JSON.stringify({ enabled: false }))
expect(readAlignmentSettings()).toEqual({ enabled: false, threshold: DEFAULT_ALIGNMENT_SETTINGS.threshold })
})
it('notifies subscribers on write', () => {
const listener = vi.fn()
const unsubscribe = subscribeAlignmentSettings(listener)
writeAlignmentSettings({ enabled: false, threshold: 8 })
expect(listener).toHaveBeenCalledWith({ enabled: false, threshold: 8 })
unsubscribe()
writeAlignmentSettings({ enabled: true, threshold: 4 })
expect(listener).toHaveBeenCalledTimes(1)
})
})