Files
feuerwerk/tests/core/key-repeat.test.ts
T
root 338ff4389f Type a repeat delay as whole milliseconds
The two On key down fields read in milliseconds now, and say so in their
labels. A number field shows three decimals unless a property asks for
fewer, so both params set decimals: 0 – the field reads 300, not 300.000,
and rounds to whole milliseconds while typing or scrubbing. The clock
behind it still counts seconds; the conversion sits at the one place that
reads the properties.
2026-09-10 05:59:39 +02:00

89 lines
3.6 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.
/**
* The repeat clock behind `On key down`, checked without a world: the cases that are awkward to
* stage in one (a key already held when tracking starts, a hostile interval) live here.
*/
import { describe, expect, it } from 'vitest';
import { nodeDef } from '../../src/core/graph';
import { MIN_REPEAT_INTERVAL, stepKeyRepeat } from '../../src/core/graph/keyRepeat';
import type { PropertyBag } from '../../src/core/model/Properties';
import { formatNumber } from '../../src/ui/math';
const held = { pressed: false, down: true, dt: 0.1, after: 0.2, interval: 0.2 };
describe('key repeat', () => {
it('starts the clock on the press, whatever the frame was worth', () => {
const step = stepKeyRepeat(null, { ...held, pressed: true, dt: 5 });
expect(step.fires).toBe(1);
expect(step.state).toEqual({ held: 0, repeats: 0 });
});
it('forgets the clock when the key comes up', () => {
expect(stepKeyRepeat({ held: 9, repeats: 3 }, { ...held, down: false })).toEqual({
state: null,
fires: 0,
});
});
it('stays quiet for a key that was already held when it started watching', () => {
// No state means the press was never seen – repeating without a first fire would be a lie.
expect(stepKeyRepeat(null, held)).toEqual({ state: null, fires: 0 });
});
it('never repeats while the delay is zero, however long the key is held', () => {
let state = { held: 0, repeats: 0 };
for (let n = 0; n < 100; n++) {
const step = stepKeyRepeat(state, { ...held, after: 0, dt: 1 });
expect(step.fires).toBe(0);
state = step.state!;
}
expect(state.held).toBe(100);
});
it('treats a negative delay as no delay at all', () => {
expect(stepKeyRepeat({ held: 5, repeats: 0 }, { ...held, after: -1 }).fires).toBe(0);
});
it('holds an absurdly small interval to the floor instead of freezing the frame', () => {
// Without the floor a 1 ns interval over a 1 s frame would ask for a billion fires.
const step = stepKeyRepeat({ held: 0.2, repeats: 0 }, { ...held, dt: 1, interval: 1e-9 });
expect(step.fires).toBeLessThanOrEqual(1 / MIN_REPEAT_INTERVAL + 1);
expect(step.fires).toBeGreaterThan(1 / MIN_REPEAT_INTERVAL - 2);
});
it('counts every due repeat when one frame swallows several', () => {
const step = stepKeyRepeat({ held: 0, repeats: 0 }, { ...held, dt: 1, after: 0.5, interval: 0.5 });
expect(step).toEqual({ state: { held: 1, repeats: 2 }, fires: 2 });
});
});
describe('the repeat rows on the node body', () => {
const hidden = (params: PropertyBag) => nodeDef('OnKeyDown')?.hiddenParams?.(params);
it('leaves both off while the repeat is switched off', () => {
expect(hidden({ key: 'a' })).toEqual(['repeatAfter', 'repeatInterval']);
expect(hidden({ key: 'a', repeatAfter: 0, repeatInterval: 50 })).toEqual([
'repeatAfter',
'repeatInterval',
]);
});
it('shows both once a delay is set, an interval of zero included', () => {
// Zero is a setting here ("every frame"), not an absence – hiding that row would lie.
expect(hidden({ key: 'a', repeatAfter: 300, repeatInterval: 0 })).toEqual([]);
});
});
describe('the repeat fields in the inspector', () => {
const param = (key: string) => {
const def = nodeDef('OnKeyDown')?.params.find((p) => p.key === key);
return def?.kind === 'number' ? def : null;
};
it('types both as whole milliseconds, so the field shows 300 and not 300.000', () => {
for (const key of ['repeatAfter', 'repeatInterval']) {
expect(param(key)?.decimals).toBe(0);
expect(formatNumber(300, param(key)?.decimals)).toBe('300');
}
});
});