openpencil/tests/engine/editor/undo/group-resize.test.ts

123 lines
3.4 KiB
TypeScript
Raw Normal View History

fix(resize): scale group and boolean children * fix(resize): scale children when resizing GROUP or BOOLEAN_OPERATION Resizing a group (or boolean operation) only moved the bounding box and rescaled the selection outline — the rendered fill stayed at its original size, so the blue outline and the filled content visibly diverged. Two root causes, both in the resize preview path: 1. During drag, children were scaled in the preview graph via updateNodePreview(), which mutates nodes without emitting a node:updated event. The renderer's vector/geometry caches (vectorPathCache, fillGeometryCache, strokeGeometryCache) were therefore never invalidated, so fills kept drawing from stale, unscaled cached paths while outlines drew live from the new dimensions. Fixed by calling renderer.invalidateVectorPath(childId) after each child preview update. 2. On commit, the final scaled values were written with updateNode() but no repaint was requested, leaving the canvas frozen on the last mismatched drag frame until the next interaction. Fixed by calling requestRepaint() after commitGroupResize(). collectDescendants() now also collects children for BOOLEAN_OPERATION nodes, which render purely from child geometry (no node-level scale), so scaling the children is the only way to scale the result — same mechanism as GROUP, no double-transform. Group resize verified visually; boolean resize verified in -browser via the editor automation API. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(editor): preserve vector undo during group resize --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Danila Poyarkov <dev@dannote.net>
2026-06-06 14:05:09 +00:00
import { describe, expect, test } from 'bun:test'
import { createEditor } from '@open-pencil/core/editor'
import type { VectorNetwork } from '@open-pencil/scene-graph'
fix(resize): scale group and boolean children * fix(resize): scale children when resizing GROUP or BOOLEAN_OPERATION Resizing a group (or boolean operation) only moved the bounding box and rescaled the selection outline — the rendered fill stayed at its original size, so the blue outline and the filled content visibly diverged. Two root causes, both in the resize preview path: 1. During drag, children were scaled in the preview graph via updateNodePreview(), which mutates nodes without emitting a node:updated event. The renderer's vector/geometry caches (vectorPathCache, fillGeometryCache, strokeGeometryCache) were therefore never invalidated, so fills kept drawing from stale, unscaled cached paths while outlines drew live from the new dimensions. Fixed by calling renderer.invalidateVectorPath(childId) after each child preview update. 2. On commit, the final scaled values were written with updateNode() but no repaint was requested, leaving the canvas frozen on the last mismatched drag frame until the next interaction. Fixed by calling requestRepaint() after commitGroupResize(). collectDescendants() now also collects children for BOOLEAN_OPERATION nodes, which render purely from child geometry (no node-level scale), so scaling the children is the only way to scale the result — same mechanism as GROUP, no double-transform. Group resize verified visually; boolean resize verified in -browser via the editor automation API. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(editor): preserve vector undo during group resize --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Danila Poyarkov <dev@dannote.net>
2026-06-06 14:05:09 +00:00
import { getNodeOrThrow } from '#tests/helpers/assert'
const originalVectorNetwork: VectorNetwork = {
vertices: [
{ x: 0, y: 0 },
{ x: 10, y: 0 },
{ x: 10, y: 10 },
{ x: 0, y: 10 }
],
segments: [
{ start: 0, end: 1, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } },
{ start: 1, end: 2, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } },
{ start: 2, end: 3, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } },
{ start: 3, end: 0, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } }
],
regions: []
}
const scaledVectorNetwork: VectorNetwork = {
...originalVectorNetwork,
vertices: originalVectorNetwork.vertices.map((vertex) => ({
x: vertex.x * 2,
y: vertex.y * 2
}))
}
describe('resize undo', () => {
test('restores single vector geometry', () => {
const editor = createEditor()
const page = editor.graph.getPages()[0]
const vector = editor.graph.createNode('VECTOR', page.id, {
x: 0,
y: 0,
width: 10,
height: 10,
vectorNetwork: originalVectorNetwork
})
editor.graph.updateNode(vector.id, {
width: 20,
height: 20,
vectorNetwork: scaledVectorNetwork
})
editor.commitResize(vector.id, {
x: 0,
y: 0,
width: 10,
height: 10,
vectorNetwork: originalVectorNetwork
})
editor.undo.undo()
const restored = getNodeOrThrow(editor.graph, vector.id)
expect(restored.width).toBe(10)
expect(restored.height).toBe(10)
expect(restored.vectorNetwork?.vertices).toEqual(originalVectorNetwork.vertices)
editor.undo.redo()
const redone = getNodeOrThrow(editor.graph, vector.id)
expect(redone.width).toBe(20)
expect(redone.height).toBe(20)
expect(redone.vectorNetwork?.vertices).toEqual(scaledVectorNetwork.vertices)
})
fix(resize): scale group and boolean children * fix(resize): scale children when resizing GROUP or BOOLEAN_OPERATION Resizing a group (or boolean operation) only moved the bounding box and rescaled the selection outline — the rendered fill stayed at its original size, so the blue outline and the filled content visibly diverged. Two root causes, both in the resize preview path: 1. During drag, children were scaled in the preview graph via updateNodePreview(), which mutates nodes without emitting a node:updated event. The renderer's vector/geometry caches (vectorPathCache, fillGeometryCache, strokeGeometryCache) were therefore never invalidated, so fills kept drawing from stale, unscaled cached paths while outlines drew live from the new dimensions. Fixed by calling renderer.invalidateVectorPath(childId) after each child preview update. 2. On commit, the final scaled values were written with updateNode() but no repaint was requested, leaving the canvas frozen on the last mismatched drag frame until the next interaction. Fixed by calling requestRepaint() after commitGroupResize(). collectDescendants() now also collects children for BOOLEAN_OPERATION nodes, which render purely from child geometry (no node-level scale), so scaling the children is the only way to scale the result — same mechanism as GROUP, no double-transform. Group resize verified visually; boolean resize verified in -browser via the editor automation API. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(editor): preserve vector undo during group resize --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Danila Poyarkov <dev@dannote.net>
2026-06-06 14:05:09 +00:00
test('restores descendant vector geometry', () => {
const editor = createEditor()
const page = editor.graph.getPages()[0]
const group = editor.graph.createNode('GROUP', page.id, {
x: 0,
y: 0,
width: 10,
height: 10
})
const vector = editor.graph.createNode('VECTOR', group.id, {
x: 0,
y: 0,
width: 10,
height: 10,
vectorNetwork: originalVectorNetwork
})
editor.graph.updateNode(group.id, { width: 20, height: 20 })
editor.graph.updateNode(vector.id, {
width: 20,
height: 20,
vectorNetwork: scaledVectorNetwork
})
editor.commitGroupResize(
group.id,
{ x: 0, y: 0, width: 10, height: 10 },
new Map([
[
vector.id,
{
x: 0,
y: 0,
width: 10,
height: 10,
vectorNetwork: originalVectorNetwork
}
]
])
)
editor.undo.undo()
const restored = getNodeOrThrow(editor.graph, vector.id)
expect(restored.width).toBe(10)
expect(restored.height).toBe(10)
expect(restored.vectorNetwork?.vertices).toEqual(originalVectorNetwork.vertices)
editor.undo.redo()
const redone = getNodeOrThrow(editor.graph, vector.id)
expect(redone.width).toBe(20)
expect(redone.height).toBe(20)
expect(redone.vectorNetwork?.vertices).toEqual(scaledVectorNetwork.vertices)
})
})