Files
feuerwerk/tests/editor/documents.test.ts
T
adminandClaude Opus 5.5 1f2b64fb4c
game-maker / gate (push) Successful in 4m2s
game-maker / suite (push) Successful in 16m14s
Show a Compressor's panel in its node, its tab on a double-click (#380)
The panel of #379 opened in a window of its own and replaced the tab.
The user wanted the device in the graph instead, and the tab of #378
back on a double-click. The switch Hardware look goes again.

- A node can show a face instead of its rows (`NodeFace`). Its inputs
  sit on the left of it and its outputs on the right, in the middle of
  the height. Each side is as wide as its longest port name.
- A Compressor node shows the panel at full size, 820x280 graph units.
  Its settings and numbers are not listed as rows, the panel has them.
- The knobs and the lever work in the node. A drag, a notch of the
  wheel and a double-click are one undo step each. A press on them
  neither picks nor moves the node, and the wheel does not zoom the
  graph. The number below a knob is only shown, typing stays in the
  tab and the inspector.
- The meter and the LEDs run while the node is in the picture of a sound
  FX or instrument graph that is seen. Out of the picture, folded or
  hidden, the node lets go of its taps.
- The window of one size, `fixedSize`, `refit`, `keptSize` and the rule
  that kept a Compressor out of the tab row are gone again.
- A knob's drag as one undo step lives in `nodeParamDrag.ts` now, for
  the tab and the node. The numbers of a node and what auto-gain says
  moved to `compressorView.ts`.

Found and fixed on the way:
- The tab and the node listen to the same taps. The tab let go of them
  on every frame it was not seen, so the node's meter stood still while
  the tab was open. Now the tab lets go once, and in a frame everything
  unseen lets go before anything seen listens.

Tests:
- Unit: compressor-view (2 new), documents (the test of `keptSize` is
  gone with it)
- e2e 34 (stein2, melektaus-atx was off): ALL PASSED, 122 checks. The
  four checks of #379 are now four of the node: ports beside the panel,
  knobs and lever in the node, the needle when played (21.7 dB with the
  tab open), and the double-click opening the tab.
- Gate (stein2): CHECK EXIT 0, 283 files, 4356 tests
- Counter-checks: an old auto-gain number read as off turns exactly its
  unit test red. A tab that lets go on every frame leaves the node's
  needle at 0 dB and no LED lit (probe on stein2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 11:41:28 +02:00

120 lines
5.0 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 {
leastBox,
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 });
});
it('is held at the least its pane needs, but never larger than the screen (#378)', () => {
const least = leastBox(700.4, 450);
expect(least).toEqual({ w: 701, h: 450 });
expect(leastBox(100, 100)).toEqual({ w: MIN_WINDOW_WIDTH, h: MIN_WINDOW_HEIGHT });
const box = { x: 100, y: 100, w: 800, h: 600 };
expect(sized(box, -500, -500, 1280, 800, least)).toEqual({ ...box, w: 701, h: 450 });
expect(onScreen({ ...box, w: 400, h: 300 }, 1280, 800, least)).toEqual({ ...box, w: 701, h: 450 });
expect(onScreen({ ...box, w: 400, h: 300 }, 600, 400, least)).toEqual({ x: 0, y: 0, w: 600, h: 400 });
});
});
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'] });
});
});