// A focused documentation item's handles for a finger: which it shows at // which size on the screen (each boundary on both sides), and where its grip // and three-dot button go — clear of each other and of the surface's edge. import { describe, expect, it } from "vitest"; import { ABOVE, gripPlace, handlePlace, HANDLES, menuPlace, shownHandles, TARGET, } from "../src/canvas/handles.js"; const SURFACE = { width: 1000, height: 800 }; const ALL = HANDLES.map(({ name }) => name); const CORNERS = ["top-left", "top-right", "bottom-right", "bottom-left"]; describe("the handles shown", () => { it("shows all eight on an item three targets wide and high", () => { expect(shownHandles({ width: 3 * TARGET, height: 3 * TARGET })).toEqual( ALL, ); }); it("shows a side's middle only from three targets of its length", () => { const narrow = shownHandles({ width: 3 * TARGET - 0.5, height: 3 * TARGET, }); expect(narrow).not.toContain("top"); expect(narrow).not.toContain("bottom"); expect(narrow).toEqual( expect.arrayContaining(["left", "right", ...CORNERS]), ); const low = shownHandles({ width: 3 * TARGET, height: 3 * TARGET - 0.5 }); expect(low).toEqual(expect.arrayContaining(["top", "bottom", ...CORNERS])); expect(low).not.toContain("left"); }); it("shows the four corners from two targets, below that only the bottom right one, so that the middle still moves it", () => { expect(shownHandles({ width: 2 * TARGET, height: 2 * TARGET })).toEqual([ "top-left", "top-right", "bottom-right", "bottom-left", ]); expect(shownHandles({ width: 2 * TARGET - 0.5, height: 400 })).toEqual([ "bottom-right", ]); expect(shownHandles({ width: 400, height: 2 * TARGET - 0.5 })).toEqual([ "bottom-right", ]); }); it("puts a target on its corner or edge, and on an item too small for that outside its corner, reaching in by 6, the dot on the corner", () => { const big = { x: 100, y: 100, width: 2 * TARGET, height: 2 * TARGET }; expect(handlePlace("bottom-right", big)).toEqual({ centre: { x: 100 + 2 * TARGET, y: 100 + 2 * TARGET }, dot: { x: 0, y: 0 }, }); expect(handlePlace("top", { ...big, width: 3 * TARGET })).toEqual({ centre: { x: 100 + 1.5 * TARGET, y: 100 }, dot: { x: 0, y: 0 }, }); const small = { x: 100, y: 100, width: 20, height: 20 }; const { centre, dot } = handlePlace("bottom-right", small); expect(centre.x - TARGET / 2).toBe(120 - 6); expect(centre.y - TARGET / 2).toBe(120 - 6); expect([centre.x + dot.x, centre.y + dot.y]).toEqual([120, 120]); }); it("names each handle's edges as its place says", () => { for (const { at, edges } of HANDLES) { expect(edges).toEqual({ left: at.x === 0, right: at.x === 1, top: at.y === 0, bottom: at.y === 1, }); } }); }); describe("the grip and the three-dot button", () => { const frame = { x: 300, y: 300, width: 200, height: 100 }; it("stand above the top corners, the button right of a narrow text’s grip", () => { const grip = gripPlace(frame, SURFACE); expect(grip).toEqual({ x: 300, y: 300 - ABOVE }); expect(menuPlace(frame, SURFACE, grip)).toEqual({ x: 500, y: 300 - ABOVE }); expect(menuPlace(frame, SURFACE, null)).toEqual({ x: 500, y: 300 - ABOVE }); const narrow = { ...frame, width: 20 }; expect(menuPlace(narrow, SURFACE, gripPlace(narrow, SURFACE))).toEqual({ x: 300 + TARGET + 4, y: 300 - ABOVE, }); }); it("move aside where the surface ends above: the grip to the left, the button to the right, each exactly at the boundary", () => { const room = TARGET / 2 + ABOVE; expect(gripPlace({ ...frame, y: room }, SURFACE)).toEqual({ x: 300, y: TARGET / 2, }); expect(gripPlace({ ...frame, y: room - 1 }, SURFACE)).toEqual({ x: 300 - ABOVE, y: room - 1, }); expect(menuPlace({ ...frame, y: room }, SURFACE, null)).toEqual({ x: 500, y: TARGET / 2, }); expect(menuPlace({ ...frame, y: room - 1 }, SURFACE, null)).toEqual({ x: 500 + ABOVE, y: room - 1, }); }); it("go below where there is no room above nor beside", () => { const cornered = { x: 30, y: 10, width: SURFACE.width - 60, height: 200 }; expect(gripPlace(cornered, SURFACE)).toEqual({ x: 30, y: 10 + 200 + ABOVE, }); expect(menuPlace(cornered, SURFACE, null)).toEqual({ x: SURFACE.width - 30, y: 10 + 200 + ABOVE, }); }); it("keep the button off the grip where both would go below", () => { const cornered = { x: 30, y: 10, width: 30, height: 200 }; const grip = gripPlace(cornered, SURFACE); const button = menuPlace(cornered, SURFACE, grip); expect( Math.abs(button.x - grip.x) >= TARGET || Math.abs(button.y - grip.y) >= TARGET, ).toBe(true); }); it("go not below where the surface ends there too", () => { const tall = { x: 30, y: 10, width: 100, height: 300 - 10 - ABOVE - TARGET / 2 + 1, }; expect(gripPlace(tall, { width: 1000, height: 300 })).toEqual({ x: 30, y: 10 - ABOVE, }); expect( gripPlace( { ...tall, height: tall.height - 1 }, { width: 1000, height: 300 }, ), ).toEqual({ x: 30, y: 300 - TARGET / 2 }); }); it("keep to the first place where nothing fits", () => { const tiny = { width: 30, height: 30 }; expect(gripPlace(frame, tiny)).toEqual({ x: 300, y: 300 - ABOVE }); }); });