import { describe, expect, it } from 'vitest'; import { AddColliderCommand } from '../../src/core/commands/colliderCommands'; import { SetOverrideCommand } from '../../src/core/commands/sceneObjectCommands'; import { AddVariableCommand } from '../../src/core/commands/variableCommands'; import { isHandleEnabled } from '../../src/core/graph'; import { findAsset, findScene } from '../../src/core/model/Project'; import { schemaFor } from '../../src/core/properties/PropertySchema'; import { resolveSceneObjectProps } from '../../src/core/properties/resolve'; import { fakeHandle, graphEditor, makeBehavior, makeProject, makeSpriteScene, place, roundTrip, runNode, startWorld, } from '../helpers'; /** A 100×100 wall at x = 200 and a 100×100 mover at the origin, both with box colliders, in a running world. */ function wallAndMover() { const f = makeSpriteScene(); const moverId = place(f, f.spriteId, { x: 0, y: 0 }); const wallId = place(f, f.spriteId, { x: 200, y: 0 }); const wallCollider = new AddColliderCommand(f.sceneId, wallId); f.stack.execute(new AddColliderCommand(f.sceneId, moverId)); f.stack.execute(wallCollider); const { world, collisions } = startWorld(f, { collisions: true }); return { world, collisions, mover: world.findByObjectId(moverId)!, wall: world.findByObjectId(wallCollider.objectId)!, }; } describe('the enabled switch', () => { it('is a common property of every placeable type, defaults to on and overrides per object', () => { expect(schemaFor('sprite')[0]).toMatchObject({ key: 'enabled', kind: 'boolean', default: true }); expect(schemaFor('behavior').map((d) => d.key)).toEqual(['enabled', 'runInEditor']); expect(schemaFor('scene').some((d) => d.key === 'enabled')).toBe(false); const f = makeSpriteScene(); const { project, stack, sceneId, spriteId, object } = f; const box = place(f, spriteId, { x: 0, y: 0 }); expect(resolveSceneObjectProps(project, findAsset(project, spriteId)!, object(box)).enabled).toBe(true); // Switching an instance off is an ordinary override – and survives a round trip. stack.execute(new SetOverrideCommand(sceneId, box, 'enabled', false)); expect(object(box).overrides).toEqual({ enabled: false }); expect(findScene(roundTrip(project), sceneId)!.objects[0]!.overrides).toEqual({ enabled: false }); stack.undo(); expect(object(box).overrides).toEqual({}); }); it('is inherited: a child of a switched-off parent is off as well', () => { const parent = fakeHandle({ enabled: false }); const child = fakeHandle({ enabled: true }, parent); expect(isHandleEnabled(child)).toBe(false); expect(child.props.enabled).toBe(true); // its own switch is untouched parent.props.enabled = true; expect(isHandleEnabled(child)).toBe(true); expect(runNode('IsEnabled', { object: child })).toEqual({ result: true, own: true }); }); it('cannot be shadowed by a behavior variable', () => { const f = makeProject(); const behavior = makeBehavior(f); expect(() => new AddVariableCommand(behavior.id, 'enabled', 'number').execute(f.project)).toThrow(); }); it('takes a collider out of the collision system while it is off', () => { const { world, collisions, wall, mover } = wallAndMover(); // The wall blocks: moving 200 to the right stops at 100 units of distance (two 100-wide boxes). collisions.moveAndSlide(world, mover, 200, 0); expect(mover.x).toBeCloseTo(100, 5); // Switched off, the wall is not there at all. mover.x = 0; wall.props.enabled = false; collisions.moveAndSlide(world, mover, 200, 0); expect(mover.x).toBeCloseTo(200, 5); }); it('stops a behavior while it is off and runs On start when it is switched on', () => { const f = makeSpriteScene(); const behavior = makeBehavior(f, 'Counter', ['ticks']); // On start and On update both add one to `ticks`. const edit = graphEditor(f, behavior); const start = edit.addNode('OnStart'); const update = edit.addNode('OnUpdate'); const one = edit.addNode('Number', { value: 1 }); const add = edit.addNode('Add'); const get = edit.variables(['ticks']); const set = edit.variables(['ticks'], { write: true }); edit.wire([ [update, 'exec', set, 'exec'], [get, 'ticks', add, 'a'], [one, 'value', add, 'b'], [add, 'result', set, 'ticks'], [start, 'exec', set, 'exec'], ]); const counter = place(f, behavior.id, { x: 0, y: 0 }); f.stack.execute(new SetOverrideCommand(f.sceneId, counter, 'enabled', false)); const { world } = startWorld(f, { behaviors: true }); const entity = world.findByObjectId(counter)!; world.tick(0.1); world.tick(0.1); expect(entity.props.ticks).toBe(0); // off: no On start, no ticks entity.props.enabled = true; // what Properties with `enabled` set does world.tick(0.1); expect(entity.props.ticks).toBe(2); // On start plus this frame's On update world.tick(0.1); expect(entity.props.ticks).toBe(3); entity.props.enabled = false; world.tick(0.1); expect(entity.props.ticks).toBe(3); // frozen again, state kept }); });