import { describe, expect, it } from 'vitest'; import { AddJointCommand } from '../../src/core/commands/colliderCommands'; import { CreateContainerCommand } from '../../src/core/commands/containerCommands'; import { MoveSceneObjectCommand, PlaceAssetCommand, SetOverrideCommand, } from '../../src/core/commands/sceneObjectCommands'; import { objectReference } from '../../src/core/model/objectRefs'; import type { PropertyValue } from '../../src/core/model/Properties'; import { collectHolderItems } from '../../src/viewport/items'; import { jointLinkOf } from '../../src/viewport/jointLinks'; import { makeSpriteScene, place } from '../helpers'; /** A box at (0, 0) with a joint 10 to its right, and a hook at (200, -100) turned by a quarter. */ function scene(values: Record, connect = true) { const f = makeSpriteScene('Box'); const box = place(f, f.spriteId, { x: 0, y: 0 }); const hook = place(f, f.spriteId, { x: 200, y: -100 }); f.stack.execute(new SetOverrideCommand(f.sceneId, hook, 'rotation', 90)); const add = new AddJointCommand(f.sceneId, box); f.stack.execute(add); const joint = add.objectId; f.stack.execute(new MoveSceneObjectCommand(f.sceneId, joint, { x: 10, y: 0, z: 0 })); if (connect) f.stack.execute(new SetOverrideCommand(f.sceneId, joint, 'connected', objectReference(hook))); for (const [key, value] of Object.entries(values)) f.stack.execute(new SetOverrideCommand(f.sceneId, joint, key, value)); const links = () => { const { items } = collectHolderItems(f.project, f.sceneOf(), 0); const byId = new Map(items.map((i) => [i.id, i])); return items.flatMap((item) => jointLinkOf(item, byId) ?? []); }; return { f, box, hook, joint, links }; } describe('what the scene view draws of a joint', () => { it('leads a hinge from where it sits to the connected object', () => { const s = scene({ jointType: 'hinge' }); expect(s.links()).toEqual([ expect.objectContaining({ id: s.joint, from: { x: 10, y: 0 }, to: { x: 200, y: -100 }, connectedId: s.hook, anchored: false, }), ]); }); it('leads a rope to its connected anchor, counted in the connected object', () => { const s = scene({ jointType: 'rope', connectedAnchorX: 50, connectedAnchorY: 0 }); const [link] = s.links(); // The hook is turned by a quarter, so 50 along its x is 50 down. expect(link!.to.x).toBeCloseTo(200); expect(link!.to.y).toBeCloseTo(-50); expect(link!.anchored).toBe(true); expect(link!.frame).not.toBeNull(); }); it('counts the anchor of a rope held to the scene in the scene', () => { const s = scene({ jointType: 'spring', connectedAnchorX: 50, connectedAnchorY: -70 }, false); expect(s.links()[0]).toMatchObject({ to: { x: 50, y: -70 }, connectedId: '', frame: null }); }); it('gives a hinge held to the scene no line: both ends are where it sits', () => { const s = scene({ jointType: 'hinge' }, false); expect(s.links()[0]).toMatchObject({ from: { x: 10, y: 0 }, to: { x: 10, y: 0 }, connectedId: '' }); }); it('has no link for anything but a joint', () => { const s = scene({}); const { items } = collectHolderItems(s.f.project, s.f.sceneOf(), 0); const byId = new Map(items.map((i) => [i.id, i])); expect(jointLinkOf(byId.get(s.box)!, byId)).toBeNull(); }); it("finds, inside each placement of a container, that placement's own copy", () => { const s = scene({ jointType: 'hinge' }); const create = new CreateContainerCommand(s.f.sceneId, [s.box, s.hook]); s.f.stack.execute(create); const first = s.f.object(create.objectId).position; s.f.stack.execute(new PlaceAssetCommand(s.f.sceneId, create.assetId, { x: first.x + 500, y: first.y })); const links = s.links().sort((a, b) => a.from.x - b.from.x); expect(links).toHaveLength(2); expect(links[1]!.from.x - links[0]!.from.x).toBeCloseTo(500); expect(links[1]!.to.x - links[0]!.to.x).toBeCloseTo(500); expect(links[0]!.connectedId).not.toBe(links[1]!.connectedId); }); });