- Refactor LayersSheet component to support mask editing state and improve layer visibility handling. - Introduce functions to collect mask layer IDs and count display layers excluding masks. - Update BrushControls to include mask view mode options and a Done button for exiting mask editing. - Modify brush session handling to support brush previews when editing masks. - Implement a new BrushPreviewRenderer for rendering brush strokes with visual feedback. - Add document geometry utilities for transforming points and resolving layer bounds. - Ensure proper cleanup of brush preview on pointer leave and other interactions.
117 lines
4.6 KiB
TypeScript
117 lines
4.6 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { commandIds } from "@commands/ids";
|
|
import type { ImageDocument } from "@core/document";
|
|
import type { PointerInputEvent } from "./pointer";
|
|
import { createTransformControlsInputController, hitTestArtboardTransformHandle, type TransformControlsEditorState } from "./transform-controls";
|
|
|
|
const ignoredState = undefined as never;
|
|
const defaultViewport = { center: { x: 0, y: 0 }, zoom: 1, rotation: 0, size: { w: 200, h: 200 } };
|
|
const defaultTools = {
|
|
activeTool: "select" as const,
|
|
interactionMode: { type: "tool" as const, tool: "select" as const },
|
|
};
|
|
|
|
describe("transform controls input", () => {
|
|
test("hit tests artboard handles and body", () => {
|
|
const bounds = { x: -50, y: -50, w: 100, h: 100 };
|
|
|
|
expect(hitTestArtboardTransformHandle({ x: 50, y: 50 }, bounds, defaultViewport)).toBe("nw");
|
|
expect(hitTestArtboardTransformHandle({ x: 100, y: 100 }, bounds, defaultViewport)).toBe("body");
|
|
expect(hitTestArtboardTransformHandle({ x: 10, y: 10 }, bounds, defaultViewport)).toBeUndefined();
|
|
});
|
|
|
|
test("does not transform while temporary pan is active", () => {
|
|
const state = createState({
|
|
selection: { artboardId: "a1", layerIds: [] },
|
|
tools: { activeTool: "select", interactionMode: { type: "temporary-pan", previousTool: "select" } },
|
|
});
|
|
const dispatched: unknown[] = [];
|
|
const controller = createTransformControlsInputController({
|
|
getDocument: () => state.document,
|
|
getEditor: () => state.editor,
|
|
dispatch: (commandId, payload) => {
|
|
dispatched.push({ commandId, payload });
|
|
return ignoredState;
|
|
},
|
|
});
|
|
|
|
expect(controller.pointerDown(pointerEvent({ position: { x: 100, y: 100 }, buttons: 1 }))).toBe(false);
|
|
expect(dispatched).toEqual([]);
|
|
});
|
|
|
|
test("crop tool resizes from handles but does not move body", () => {
|
|
const state = createState({
|
|
selection: { artboardId: "a1", layerIds: [] },
|
|
tools: { activeTool: "crop", interactionMode: { type: "tool", tool: "crop" } },
|
|
});
|
|
const dispatched: unknown[] = [];
|
|
const controller = createTransformControlsInputController({
|
|
getDocument: () => state.document,
|
|
getEditor: () => state.editor,
|
|
dispatch: (commandId, payload) => {
|
|
dispatched.push({ commandId, payload });
|
|
return ignoredState;
|
|
},
|
|
});
|
|
|
|
expect(controller.pointerDown(pointerEvent({ position: { x: 100, y: 100 }, buttons: 1 }))).toBe(false);
|
|
expect(controller.pointerDown(pointerEvent({ position: { x: 150, y: 150 }, buttons: 1 }))).toBe(true);
|
|
});
|
|
|
|
test("dispatches transform lifecycle for selected artboard", () => {
|
|
let state = createState({ selection: { artboardId: "a1", layerIds: [] } });
|
|
const dispatched: unknown[] = [];
|
|
const controller = createTransformControlsInputController({
|
|
getDocument: () => state.document,
|
|
getEditor: () => state.editor,
|
|
dispatch: (commandId, payload) => {
|
|
dispatched.push({ commandId, payload });
|
|
if (commandId === commandIds.transformBegin) state = { ...state, editor: { ...state.editor, transformSession: payload } };
|
|
return ignoredState;
|
|
},
|
|
});
|
|
|
|
expect(controller.pointerDown(pointerEvent({ position: { x: 100, y: 100 }, buttons: 1 }))).toBe(true);
|
|
expect(controller.pointerMove(pointerEvent({ position: { x: 110, y: 120 }, buttons: 1, shiftKey: true }))).toBe(true);
|
|
expect(controller.pointerUp(pointerEvent({ position: { x: 110, y: 120 }, buttons: 0 }))).toBe(true);
|
|
expect(dispatched.map((event) => (event as { commandId: string }).commandId)).toEqual([
|
|
commandIds.transformBegin,
|
|
commandIds.transformUpdate,
|
|
commandIds.transformEnd,
|
|
]);
|
|
expect((dispatched[1] as { payload: { shiftKey: boolean } }).payload.shiftKey).toBe(true);
|
|
});
|
|
});
|
|
|
|
function createState(editorOverrides: Partial<TransformControlsEditorState> = {}): { document: ImageDocument; editor: TransformControlsEditorState } {
|
|
return {
|
|
document: {
|
|
id: "d1",
|
|
name: "Test",
|
|
version: 1,
|
|
assets: [],
|
|
artboards: [{ id: "a1", name: "Artboard", bounds: { x: -50, y: -50, w: 100, h: 100 }, backgroundColor: "transparent", visible: true, locked: false, layers: [] }],
|
|
},
|
|
editor: {
|
|
viewport: defaultViewport,
|
|
selection: { layerIds: [] },
|
|
tools: defaultTools,
|
|
...editorOverrides,
|
|
},
|
|
};
|
|
}
|
|
|
|
function pointerEvent(overrides: Partial<PointerInputEvent>): PointerInputEvent {
|
|
return {
|
|
pointerId: 1,
|
|
pointerType: "mouse",
|
|
position: { x: 0, y: 0 },
|
|
buttons: 0,
|
|
altKey: false,
|
|
ctrlKey: false,
|
|
metaKey: false,
|
|
shiftKey: false,
|
|
...overrides,
|
|
};
|
|
}
|