packages/editor (React 18, Vite) draws a document as Java FILIUS does: Java's default hardware icons, the device button of JSidebarButton, cables as JCablePanel's quadratic curve between the buttons' middles, wireless cables dashed, documentation items below. The surface moves with mouse and finger (the pointer captured only once it moved) and zooms with Ctrl and the wheel, a trackpad pinch, two fingers and the zoom bar; the first view shows all. German and English catalogs with Java's device names, the dashboard's colour tokens, the bridge with its origin check. packages/doc gets the drawing's light view of a document. Java's Switch.cloud (the Internet in the examples) was lost on reading; it is now read, kept in the model and written, with FlsOracle and the corpus, written and exported checks renewed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
60 lines
3.0 KiB
TypeScript
60 lines
3.0 KiB
TypeScript
// The view onto the surface: zoom steps and limits, zooming around a point,
|
|
// fitting the content in.
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
import { clampZoom, fitted, MAX_ZOOM, middle, MIN_ZOOM, shifted, spread, toScreen, toSurface, zoomedAt, zoomIn, zoomOut, ZOOM_STEPS } from '../src/canvas/viewport.js';
|
|
|
|
describe('the view onto the surface', () => {
|
|
it('keeps the zoom between a quarter and twice', () => {
|
|
expect([clampZoom(0.1), clampZoom(1.3), clampZoom(9)]).toEqual([MIN_ZOOM, 1.3, MAX_ZOOM]);
|
|
});
|
|
|
|
it('steps along the ladder from any value, and back to where it was', () => {
|
|
expect(ZOOM_STEPS.map(zoomIn)).toEqual([0.5, 0.75, 1, 1.25, 1.5, 2, 2]);
|
|
expect(ZOOM_STEPS.map(zoomOut)).toEqual([0.25, 0.25, 0.5, 0.75, 1, 1.25, 1.5]);
|
|
expect([zoomIn(0.8), zoomOut(0.8), zoomIn(1.0005)]).toEqual([1, 0.75, 1.25]);
|
|
for (const step of ZOOM_STEPS.slice(1, -1)) expect(zoomOut(zoomIn(step))).toBe(step);
|
|
});
|
|
|
|
it('turns screen and surface pixels into each other', () => {
|
|
const view = { scale: 0.5, x: 100, y: -20 };
|
|
expect(toScreen(view, { x: 40, y: 80 })).toEqual({ x: 120, y: 20 });
|
|
expect(toSurface(view, toScreen(view, { x: 40, y: 80 }))).toEqual({ x: 40, y: 80 });
|
|
});
|
|
|
|
it('zooms so that the point zoomed at stays put, within the limits', () => {
|
|
const view = { scale: 1, x: 30, y: 40 };
|
|
const point = { x: 200, y: 150 };
|
|
const zoomed = zoomedAt(view, 1.5, point);
|
|
expect(zoomed.scale).toBe(1.5);
|
|
expect(toScreen(zoomed, toSurface(view, point))).toEqual(point);
|
|
expect(zoomedAt(view, 10, point).scale).toBe(MAX_ZOOM);
|
|
expect(toScreen(zoomedAt(view, 10, point), toSurface(view, point))).toEqual(point);
|
|
});
|
|
|
|
it('moves by what the pointer moved', () => {
|
|
expect(shifted({ scale: 2, x: 1, y: 2 }, { x: 10, y: -5 })).toEqual({ scale: 2, x: 11, y: -3 });
|
|
});
|
|
|
|
it('fits the content in the middle with a margin, never above 100 %', () => {
|
|
const content = { x: 100, y: 50, width: 800, height: 400 };
|
|
const view = fitted(content, { width: 448, height: 448 });
|
|
expect(view.scale).toBe(0.5);
|
|
// Left and right margin alike, the content in the middle vertically.
|
|
expect(toScreen(view, { x: 100, y: 50 })).toEqual({ x: 24, y: 124 });
|
|
expect(toScreen(view, { x: 900, y: 450 })).toEqual({ x: 424, y: 324 });
|
|
expect(fitted({ x: 0, y: 0, width: 100, height: 100 }, { width: 1000, height: 1000 }).scale).toBe(1);
|
|
expect(fitted({ x: 0, y: 0, width: 100_000, height: 10 }, { width: 500, height: 500 }).scale).toBe(MIN_ZOOM);
|
|
});
|
|
|
|
it('shows an empty surface at 100 % with its origin at the margin', () => {
|
|
expect(fitted({ x: 0, y: 0, width: 0, height: 0 }, { width: 500, height: 500 })).toEqual({ scale: 1, x: 24, y: 24 });
|
|
expect(fitted({ x: 10, y: 10, width: 50, height: 50 }, { width: 0, height: 0 })).toEqual({ scale: 1, x: 14, y: 14 });
|
|
});
|
|
|
|
it('measures two fingers', () => {
|
|
expect(spread({ x: 0, y: 0 }, { x: 3, y: 4 })).toBe(5);
|
|
expect(middle({ x: 0, y: 10 }, { x: 4, y: 20 })).toEqual({ x: 2, y: 15 });
|
|
});
|
|
});
|