/** * `Key was pressed` and `Key was released` answer the edges `On key down` and `On key up` fire on, * as values a flow can ask for. Driven through a real world, so the frame they hold for is the * one the run really ends. */ import { describe, expect, it } from 'vitest'; import type { BehaviorAsset } from '../../src/core/model/Asset'; import { graphEditor, makeBehavior, makeSpriteScene, place, startWorld, type SceneFixture } from '../helpers'; function scene(): SceneFixture & { behavior: BehaviorAsset } { const f = makeSpriteScene('Box'); const behavior = makeBehavior(f, 'Logic'); const box = place(f, f.spriteId, { x: 0, y: 0 }); place(f, behavior.id, { x: 0, y: 0 }, box); return { ...f, behavior }; } /** Wires `On update → Branch(.) → Log(message)`: one log entry per frame the node says yes. */ function logWhile( f: SceneFixture & { behavior: BehaviorAsset }, type: string, port: string, key: string, message: string, ): void { const edit = graphEditor(f, f.behavior); const update = edit.addNode('OnUpdate'); const asked = edit.addNode(type, { key }); const branch = edit.addNode('Branch'); const log = edit.addNode('Log', { message }); edit.connect(update, 'exec', branch, 'exec'); edit.connect(asked, port, branch, 'condition'); edit.connect(branch, 'then', log, 'exec'); } describe('Key was pressed', () => { it('is true in the frame the key goes down and in no other', () => { const f = scene(); logWhile(f, 'KeyWasPressed', 'pressed', 'a', 'pressed'); const { world, input, frame } = startWorld(f, { behaviors: true }); frame(); expect(world.log).toEqual([]); input.press('a'); frame(); expect(world.log).toEqual(['pressed']); frame(); frame(); expect(world.log).toEqual(['pressed']); }); it("does not count the browser's own repeat of a held key as a new press", () => { const f = scene(); logWhile(f, 'KeyWasPressed', 'pressed', 'a', 'pressed'); const { world, input, frame } = startWorld(f, { behaviors: true }); input.press('a'); frame(); input.press('a'); frame(); expect(world.log).toEqual(['pressed']); input.release('a'); input.press('a'); frame(); expect(world.log).toEqual(['pressed', 'pressed']); }); it('answers in the same frame as On key down', () => { const f = scene(); logWhile(f, 'KeyWasPressed', 'pressed', 'a', 'value'); const edit = graphEditor(f, f.behavior); edit.connect( edit.addNode('OnKeyDown', { key: 'a' }), 'exec', edit.addNode('Log', { message: 'event' }), 'exec', ); const { world, input, frame } = startWorld(f, { behaviors: true }); input.press('a'); frame(); expect([...world.log].sort()).toEqual(['event', 'value']); frame(); expect(world.log).toHaveLength(2); }); it('takes "a" and "A" for one key and leaves another key alone', () => { const f = scene(); logWhile(f, 'KeyWasPressed', 'pressed', 'a', 'pressed'); const { world, input, frame } = startWorld(f, { behaviors: true }); input.press('b'); frame(); expect(world.log).toEqual([]); input.press('A'); frame(); expect(world.log).toEqual(['pressed']); }); it('reads the key on the node the same way, whichever case it was saved in', () => { const f = scene(); logWhile(f, 'KeyWasPressed', 'pressed', 'A', 'pressed'); logWhile(f, 'KeyWasReleased', 'released', 'A', 'released'); const { world, input, frame } = startWorld(f, { behaviors: true }); input.press('a'); frame(); input.release('a'); frame(); expect(world.log).toEqual(['pressed', 'released']); }); it('stays false while the key is only released', () => { const f = scene(); logWhile(f, 'KeyWasPressed', 'pressed', 'a', 'pressed'); const { world, input, frame } = startWorld(f, { behaviors: true }); input.press('a'); frame(); input.release('a'); frame(); expect(world.log).toEqual(['pressed']); }); }); describe('Key was released', () => { it('is true in the frame the key comes up and in no other', () => { const f = scene(); logWhile(f, 'KeyWasReleased', 'released', 'a', 'released'); const { world, input, frame } = startWorld(f, { behaviors: true }); input.press('a'); frame(); frame(); expect(world.log).toEqual([]); input.release('a'); frame(); expect(world.log).toEqual(['released']); frame(); expect(world.log).toEqual(['released']); }); it('stays false for a key that was never down', () => { const f = scene(); logWhile(f, 'KeyWasReleased', 'released', 'a', 'released'); const { world, input, frame } = startWorld(f, { behaviors: true }); input.release('a'); frame(); expect(world.log).toEqual([]); }); it('sees a press and a release inside one frame as both', () => { const f = scene(); logWhile(f, 'KeyWasPressed', 'pressed', 'a', 'pressed'); logWhile(f, 'KeyWasReleased', 'released', 'a', 'released'); const { world, input, frame } = startWorld(f, { behaviors: true }); input.press('a'); input.release('a'); frame(); expect([...world.log].sort()).toEqual(['pressed', 'released']); }); it('is true for every held key when the window loses the keyboard', () => { const f = scene(); logWhile(f, 'KeyWasReleased', 'released', 'a', 'released'); const { world, input, frame } = startWorld(f, { behaviors: true }); input.press('a'); frame(); input.releaseAll(); frame(); expect(world.log).toEqual(['released']); }); });