Files
feuerwerk/tests/editor/documents.test.ts
T
adminandClaude Opus 5.5 76f5097a38
game-maker / gate (push) Successful in 3m54s
game-maker / suite (push) Successful in 15m17s
Float a document's tab in a window of its own, and dock it (#371)
The Parametric EQ has a tab of its own since #370, but Play is in the
sound FX's tab. A tab that can float keeps both in view. There is one
editor per kind, so the kind's pane is lent to the window and given
back on Dock, the way fullscreen moves the game view.

- A right-click on a document's tab opens a menu with Float and Close.
  `DocumentTab` passes the press to `TabStrip`'s new `onMenu`.
- `FloatWindow` sits on the overlay. Its title bar moves it, its lower
  right corner sizes it, Dock and the x close it. A press brings it to
  the front.
- `floating.ts` is the pure part: where a window opens, held whole on
  the screen, sized down to 320 x 200. Nothing of it is remembered.
- One window per kind. Another document of that kind opens there and
  closes the one it had. Its tab, if it had one, leaves the row.
- When the row showed the kind, it shows the nearest document whose
  kind is not in a window, else the scene (`neighbourOf`).
- Delete, Copy and Paste go to the window pressed last, and back to
  the tab showing after a press in the editor (`keyTab`).
- Previews tick and redraw for a kind in a window as for the tab
  showing. Fullscreen hides the windows. Loading a project or signing
  out closes them.
- Only documents float, Scene and Game stay in the row.
- Hooks: `floatWindows`, `floatDocument`, `keyTarget`.

Tests:
- Unit documents: 16 green, 5 new. Counter-check: `neighbourOf`
  without the window check turns its test red.
- e2e 44 (melektaus-atx, after steps 1-12 and 37): ALL PASSED,
  505 checks in the chain, 6 new for float, move, size, another
  texture, keys, Dock and x.
- Gate (melektaus-atx): CHECK EXIT 0, 274 files, 4236 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 14:27:12 +02:00

103 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest';
import { closeDocument, neighbourOf, openDocument, replaceDocument } from '../../src/editor/documents';
import { MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH, onScreen, openingBox, sized } from '../../src/editor/floating';
import { tabRowLayout } from '../../src/editor/tabRow';
const row = (docs: string[], active: string | null) => ({ docs, active });
describe('open documents', () => {
it('puts a new document right of the one showing', () => {
expect(openDocument(row(['a', 'b', 'c'], 'a'), 'x')).toEqual(row(['a', 'x', 'b', 'c'], 'x'));
});
it('puts it last while a fixed tab is showing', () => {
expect(openDocument(row(['a', 'b'], null), 'x')).toEqual(row(['a', 'b', 'x'], 'x'));
expect(openDocument(row([], null), 'x')).toEqual(row(['x'], 'x'));
});
it('only brings an open document up – one tab per document', () => {
expect(openDocument(row(['a', 'b', 'c'], 'c'), 'a')).toEqual(row(['a', 'b', 'c'], 'a'));
});
it('shows the left neighbour after the one showing closes', () => {
expect(closeDocument(row(['a', 'b', 'c'], 'b'), 'b')).toEqual(row(['a', 'c'], 'a'));
});
it('shows the right one when the first closes, and nothing when the last goes', () => {
expect(closeDocument(row(['a', 'b'], 'a'), 'a')).toEqual(row(['b'], 'b'));
expect(closeDocument(row(['a'], 'a'), 'a')).toEqual(row([], null));
});
it('keeps what is showing when another tab closes', () => {
expect(closeDocument(row(['a', 'b', 'c'], 'c'), 'a')).toEqual(row(['b', 'c'], 'c'));
expect(closeDocument(row(['a', 'b'], null), 'b')).toEqual(row(['a'], null));
});
it('ignores a document that is not open', () => {
const before = row(['a'], 'a');
expect(closeDocument(before, 'x')).toBe(before);
});
it('lets a tab go on to another document in its place', () => {
expect(replaceDocument(row(['a', 'b', 'c'], 'b'), 'b', 'x')).toEqual(row(['a', 'x', 'c'], 'x'));
});
it('drops the other tab of a document it goes on to', () => {
expect(replaceDocument(row(['a', 'b', 'c'], 'a'), 'a', 'c')).toEqual(row(['c', 'b'], 'c'));
});
it('leaves the row alone for a tab that is not open', () => {
const before = row(['a'], 'a');
expect(replaceDocument(before, 'x', 'y')).toBe(before);
expect(replaceDocument(before, 'a', 'a')).toBe(before);
});
});
describe('what shows when a document goes to a window (#371)', () => {
const all = () => true;
it('picks the left neighbour, else the right one, else none', () => {
expect(neighbourOf(['a', 'c'], 1, all)).toBe('a');
expect(neighbourOf(['b', 'c'], 0, all)).toBe('b');
expect(neighbourOf([], 0, all)).toBeNull();
});
it('passes over documents whose kind is in a window', () => {
const tabs = (doc: string) => !doc.startsWith('fx');
expect(neighbourOf(['a', 'fx1', 'fx2', 'b'], 3, tabs)).toBe('a');
expect(neighbourOf(['fx1', 'fx2', 'b'], 1, tabs)).toBe('b');
expect(neighbourOf(['fx1', 'fx2'], 1, tabs)).toBeNull();
});
});
describe('a tab in a window (#371)', () => {
it('opens in the middle of the screen, every further window a step lower right', () => {
expect(openingBox(1280, 800, 0)).toEqual({ x: 320, y: 160, w: 640, h: 480 });
expect(openingBox(1280, 800, 2)).toEqual({ x: 368, y: 208, w: 640, h: 480 });
});
it('is held whole on the screen when it is moved past an edge', () => {
expect(onScreen({ x: 900, y: -40, w: 640, h: 480 }, 1280, 800)).toEqual({ x: 640, y: 0, w: 640, h: 480 });
expect(onScreen({ x: 100, y: 100, w: 640, h: 480 }, 500, 400)).toEqual({ x: 0, y: 0, w: 500, h: 400 });
});
it('is sized from its lower right corner, down to a floor and out to the screen’s edge', () => {
const box = { x: 600, y: 300, w: 400, h: 300 };
expect(sized(box, 100, 50, 1280, 800)).toEqual({ ...box, w: 500, h: 350 });
expect(sized(box, -500, -500, 1280, 800)).toEqual({ ...box, w: MIN_WINDOW_WIDTH, h: MIN_WINDOW_HEIGHT });
expect(sized(box, 900, 900, 1280, 800)).toEqual({ ...box, w: 680, h: 500 });
});
});
describe('tab row without an active tab', () => {
it('fills the row in order when a kind with no document is showing', () => {
const tabs = [
{ key: 'a', width: 50 },
{ key: 'b', width: 50 },
{ key: 'c', width: 50 },
];
const layout = tabRowLayout({ tabs, available: 132, gap: 2, overflowWidth: 28, active: null });
expect(layout).toEqual({ shown: ['a', 'b'], hidden: ['c'] });
});
});