/** * The magnet in the tab headers (#194): one icon toggle for every tab that snaps, graph canvas and * vector art alike. Driven through the button's own `onClick`, the path a click takes. */ import { beforeAll, describe, expect, it, vi } from 'vitest'; import { CanvasTextMetrics } from 'pixi.js'; import { GRAPH_SNAPS_AT_FIRST, SnapToggle } from '../../src/editor/graph/SnapToggle'; import { Button, HStack, Theme } from '../../src/ui'; // Node has no DOMParser to read the file with; what the magnet looks like is the icon tests' job. vi.mock('../../src/ui/svg', () => ({ drawSvg: () => undefined, tintSvg: (svg: string) => svg })); beforeAll(() => { CanvasTextMetrics.measureText = (() => ({ width: 10, height: 10, lines: [], lineWidths: [] })) as never; }); function target() { const calls: boolean[] = []; return { calls, setSnapping: (on: boolean) => calls.push(on) }; } describe('SnapToggle', () => { it('is a square icon button without a caption', () => { const snap = new SnapToggle(() => target()); expect(snap.widgets).toEqual([snap.button]); expect(snap.button.measure().w).toBe(Theme.controlHeight); expect(snap.button.text).toBe(''); }); it('starts off and switches the target on and off again, lit while on', () => { const canvas = target(); const snap = new SnapToggle(() => canvas); expect(snap.button.toggled).toBe(false); snap.button.onClick?.(); expect(snap.button.toggled).toBe(true); snap.button.onClick?.(); expect(snap.button.toggled).toBe(false); expect(canvas.calls).toEqual([true, false]); }); it('starts lit where its target snaps from the start, as every graph does (#391)', () => { const canvas = target(); const snap = new SnapToggle(() => canvas, GRAPH_SNAPS_AT_FIRST); expect(GRAPH_SNAPS_AT_FIRST).toBe(true); expect(snap.button.toggled).toBe(true); expect(snap.snapping).toBe(true); // Nothing is asked of the target before a click: it snaps by itself from the start. expect(canvas.calls).toEqual([]); snap.button.onClick?.(); expect(snap.button.toggled).toBe(false); expect(canvas.calls).toEqual([false]); }); it('has Arrange beside it in a graph’s tab, greyed out with it, arranging the target of the click (#429)', () => { let arranged = 0; const canvas = { ...target(), arrange: () => (arranged += 1) }; const snap = new SnapToggle(() => canvas, GRAPH_SNAPS_AT_FIRST, true); expect(snap.widgets).toEqual([snap.button, snap.arrangeButton]); expect(snap.arrangeButton!.text).toBe('Arrange'); snap.arrangeButton!.onClick?.(); expect(arranged).toBe(1); snap.setEnabled(false); expect(snap.arrangeButton!.enabled).toBe(false); expect(new SnapToggle(() => canvas).arrangeButton).toBeNull(); }); it('hides Arrange from a header it shares, which then lays the rest out without it', () => { const snap = new SnapToggle(() => target(), GRAPH_SNAPS_AT_FIRST, true); const header = new HStack({ gap: 4 }); const fit = new Button('Fit'); for (const widget of [...snap.widgets, fit]) header.add(widget); header.resize(400, Theme.controlHeight); const shownAt = fit.x; snap.setArrangeShown(false); expect(snap.arrangeButton!.visible).toBe(false); expect(fit.x).toBeLessThan(shownAt); snap.setArrangeShown(true); expect(fit.x).toBe(shownAt); new SnapToggle(() => target()).setArrangeShown(false); }); it('asks for the target at the click, so a canvas mounted later is the one switched', () => { let canvas = target(); const snap = new SnapToggle(() => canvas); const later = target(); canvas = later; snap.set(true); expect(later.calls).toEqual([true]); }); });