Pause is a sub-state of play, not a third mode: the toolbar button and Ctrl+Enter stop ticking the world, the clock included. A held frame still drops the input edges, so resuming does not fire a pause worth of keys in one go. The editor run missed the case it is most wanted for. hasEditorRun only looked at the stored scene objects, so a behavior inside a placed container never started one; the world now builds from resolveObjects and writes back through objectRef, keeping the move as an instance edit instead of baking the instance out. Keys and clicks go to the run while the Game tab is on screen, where no key means anything to the editor, and are handed back on leaving it - held keys released, so nothing keeps walking unpressed.
93 lines
2.9 KiB
TypeScript
93 lines
2.9 KiB
TypeScript
/**
|
||
* Pausing play. The hold itself is a sub-state of play mode, so these tests watch that it can
|
||
* only exist while playing and that leaving play always clears it – a stuck "paused" would
|
||
* leave the editor unable to run anything.
|
||
*/
|
||
import { describe, expect, it } from 'vitest';
|
||
import { EditorState } from '../../src/editor/state';
|
||
import { makeSpriteScene, place, startWorld } from '../helpers';
|
||
|
||
function playing() {
|
||
const f = makeSpriteScene('Box');
|
||
place(f, f.spriteId, { x: 0, y: 0 });
|
||
const state = new EditorState();
|
||
state.loadProject(f.project, 1);
|
||
state.enterPlay();
|
||
return { f, state };
|
||
}
|
||
|
||
describe('pausing the game', () => {
|
||
it('holds and releases while playing', () => {
|
||
const { state } = playing();
|
||
expect(state.paused).toBe(false);
|
||
state.setPaused(true);
|
||
expect(state.paused).toBe(true);
|
||
state.setPaused(false);
|
||
expect(state.paused).toBe(false);
|
||
});
|
||
|
||
it('cannot be paused while editing', () => {
|
||
const f = makeSpriteScene('Box');
|
||
const state = new EditorState();
|
||
state.loadProject(f.project, 1);
|
||
state.setPaused(true);
|
||
expect(state.paused).toBe(false);
|
||
});
|
||
|
||
it('tells the toolbar so its button can change label', () => {
|
||
const { state } = playing();
|
||
let changes = 0;
|
||
state.on('mode', () => (changes += 1));
|
||
state.setPaused(true);
|
||
expect(changes).toBe(1);
|
||
// Setting the same value again is not a change and must not redraw.
|
||
state.setPaused(true);
|
||
expect(changes).toBe(1);
|
||
});
|
||
|
||
it('is over when play is over, so the next run starts moving', () => {
|
||
const { state } = playing();
|
||
state.setPaused(true);
|
||
state.exitPlay();
|
||
expect(state.paused).toBe(false);
|
||
state.enterPlay();
|
||
expect(state.paused).toBe(false);
|
||
});
|
||
|
||
it('does not survive loading another project', () => {
|
||
const { state } = playing();
|
||
state.setPaused(true);
|
||
state.loadProject(makeSpriteScene('Other').project, 1);
|
||
expect(state.paused).toBe(false);
|
||
});
|
||
});
|
||
|
||
describe('the world while it is not ticked', () => {
|
||
it('keeps its clock and its positions', () => {
|
||
const f = makeSpriteScene('Box');
|
||
place(f, f.spriteId, { x: 0, y: 0 });
|
||
const { world } = startWorld(f);
|
||
world.tick(0.5);
|
||
const box = world.entities[0]!;
|
||
const time = world.time;
|
||
const x = box.x;
|
||
// What a paused frame does: no tick at all.
|
||
expect(world.time).toBe(time);
|
||
expect(box.x).toBe(x);
|
||
world.tick(0.5);
|
||
expect(world.time).toBeCloseTo(time + 0.5, 5);
|
||
});
|
||
|
||
it('forgets a click that arrived while nothing was ticking', () => {
|
||
const f = makeSpriteScene('Box');
|
||
place(f, f.spriteId, { x: 0, y: 0 });
|
||
const { world } = startWorld(f);
|
||
world.setPointerDown(true);
|
||
expect(world.pointer.pressed).toBe(true);
|
||
world.dropPointerEdges();
|
||
expect(world.pointer.pressed).toBe(false);
|
||
// The button is still held – only the one-frame edge is gone.
|
||
expect(world.pointer.down).toBe(true);
|
||
});
|
||
});
|