Files
feuerwerk/tests/editor/documents.test.ts
T
adminandClaude Opus 5.5 9917253751 Open a Compressor as a device in a window of one size (#379)
The tab from #378 shows a Compressor as an editor. The user wanted it
to look like a device as well, after a brushed aluminium plug-in, with
a meter of the gain reduction instead of the pictures. The tab stays
and is switched to per node.

- Hardware look is a switch under Settings, on by default for new and
  existing Compressors. Only the editor reads it.
- The panel is 820x280 px and opens straight in a window of that size.
  The window has no sizing corner and no Dock. A pane says so through
  `fixedSize`, and `keptSize` keeps the window's middle when it
  changes to that size.
- A Compressor in this look never sits in the tab row. Opening one
  floats it, and switching the look on moves its tab into the window.
- The panel has chrome knobs (threshold and makeup large), a meter
  with a needle that moves like a VU's, an LCD with the name and the
  reading, a lever with a lamp for auto-gain, and LEDs for the level
  going out. Everything is drawn in code.
- The Knob takes a size and a chrome look, and leaves out its caption
  when it has no name, so the panel engraves its own.
- `autoGainOn` is now `switchOn`, because the new switch reads the
  same way.
- Where parts sit, the meter's scale, the needle's speed, the LEDs and
  the plate's grain are in `hardwareView.ts`, apart from the drawing.

Decided myself:
- The meter's scale runs by the square root, so the first dB is about
  as wide as the last nine. It is red from 10 dB on.
- The LEDs light one every 3 dB from -39 to 0 dB and fall by 20 dB a
  second.
- Switching the look changes the graph's fingerprint, like any other
  setting of a node. The sound FX is built again once.

Findings, fixed on the way:
- Pixi's `arc` goes on from the last point of the path. The meter's
  scale had a line from the corner of the face until each arc got a
  `moveTo` to its own start.
- A text put into a Graphics makes Pixi warn that only Containers take
  children. The LCD is a Container now.
- The first browser check read the needle at one moment and missed the
  short sound. The panel keeps the most its needle and LEDs showed.

Tests:
- Unit: hardware-view (10 new), documents (1 new)
- e2e 34 (melektaus-atx): ALL PASSED, 122 checks (4 new). Played,
  the needle reached 20.9 dB and 5 LEDs lit.
- Gate (melektaus-atx): CHECK EXIT 0, 283 files, 4355 tests (11 new)
- Counter-checks: a window that keeps its corner instead of its
  middle, and a linear meter scale, each turn exactly their test red.

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

128 lines
5.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 {
keptSize,
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 });
});
it('takes the size of a pane of one size around the middle it had, and keeps a box of that size (#379)', () => {
const box = { x: 100, y: 100, w: 800, h: 600 };
expect(keptSize(box, { w: 400, h: 200 })).toEqual({ x: 300, y: 300, w: 400, h: 200 });
const kept = { x: 7, y: 9, w: 400, h: 200 };
expect(keptSize(kept, { w: 400, h: 200 })).toBe(kept);
});
});
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'] });
});
});