/** * The two pure halves of the `asset` property kind: how a stored id reads, and which assets a * picker may offer for it. Used by the inspector row and the graph node view alike. */ import { describe, expect, it } from 'vitest'; import { CreateAssetCommand } from '../../src/core/commands/assetCommands'; import { BUILT_IN_NODES } from '../../src/core/graph/nodes'; import type { AssetType } from '../../src/core/model/AssetTypes'; import type { Project } from '../../src/core/model/Project'; import { assetChoiceLabel, assetRefChoices, assetRefLabel } from '../../src/core/properties/assetRef'; import { makeProject, type ProjectFixture } from '../helpers'; function add(fixture: ProjectFixture, type: AssetType, name: string): string { const command = new CreateAssetCommand(type, name); fixture.stack.execute(command); return command.asset.id; } /** A project holding one asset of every type, each named after its type. */ function oneOfEach(): { project: Project; ids: Record } { const fixture = makeProject(); const types: AssetType[] = [ 'scene', 'camera', 'sprite', 'text', 'behavior', 'collider', 'container', 'texture', ]; const ids = Object.fromEntries(types.map((type) => [type, add(fixture, type, type)])) as Record< AssetType, string >; return { project: fixture.project, ids }; } describe('assetRefLabel', () => { it('reads as the referenced asset name', () => { const fixture = makeProject(); const id = add(fixture, 'sprite', 'Coin'); expect(assetRefLabel(fixture.project, id)).toBe('Coin'); }); it('reads as "None" when nothing is referenced', () => { expect(assetRefLabel(makeProject().project, '')).toBe('None'); }); it('keeps showing the id of a reference that no longer resolves', () => { expect(assetRefLabel(makeProject().project, 'gone')).toBe('gone?'); }); it('marks a deleted asset as dangling rather than falling back to "None"', () => { const fixture = makeProject(); const id = add(fixture, 'sprite', 'Coin'); fixture.stack.undo(); expect(assetRefLabel(fixture.project, id)).toBe(`${id}?`); }); it('dangles without a project, but the empty reference is still "None"', () => { expect(assetRefLabel(null, 'x')).toBe('x?'); expect(assetRefLabel(null, '')).toBe('None'); }); }); describe('assetRefChoices', () => { it('offers every placeable asset when no types are given', () => { const { project } = oneOfEach(); expect(assetRefChoices(project).map((a) => a.type)).toEqual([ 'behavior', 'camera', 'collider', 'container', 'sprite', 'text', ]); }); it('never offers a scene or a texture as a placeable asset', () => { const { project } = oneOfEach(); const types = assetRefChoices(project).map((a) => a.type); expect(types).not.toContain('scene'); expect(types).not.toContain('texture'); }); it('offers exactly the given types, placeable or not', () => { const { project, ids } = oneOfEach(); expect(assetRefChoices(project, ['texture']).map((a) => a.id)).toEqual([ids.texture]); expect(assetRefChoices(project, ['scene', 'sprite']).map((a) => a.id)).toEqual([ids.scene, ids.sprite]); }); it('offers nothing when no asset has one of the given types', () => { const fixture = makeProject(); add(fixture, 'sprite', 'Coin'); expect(assetRefChoices(fixture.project, ['texture'])).toEqual([]); }); it('sorts by name, not by creation order', () => { const fixture = makeProject(); add(fixture, 'sprite', 'Zebra'); add(fixture, 'sprite', 'Apple'); add(fixture, 'text', 'Mango'); expect(assetRefChoices(fixture.project).map((a) => a.name)).toEqual(['Apple', 'Mango', 'Zebra']); }); it('sorts namesakes by type so the order does not depend on creation order', () => { const fixture = makeProject(); add(fixture, 'text', 'Coin'); add(fixture, 'sprite', 'Coin'); expect(assetRefChoices(fixture.project).map(assetChoiceLabel)).toEqual(['Coin (sprite)', 'Coin (text)']); }); it('leaves the asset order of the project alone', () => { const fixture = makeProject(); add(fixture, 'sprite', 'Zebra'); add(fixture, 'sprite', 'Apple'); assetRefChoices(fixture.project); expect(fixture.project.assets.map((a) => a.name)).toEqual(['Zebra', 'Apple']); }); }); describe('node asset params', () => { // Guards against the old second notion of an asset reference: an enum with empty options // that the graph inspector filled in. it('use the asset kind and offer every placeable asset', () => { const params = BUILT_IN_NODES.flatMap((def) => def.params).filter((p) => p.key === 'asset'); expect(params.length).toBeGreaterThan(0); for (const param of params) { expect(param.kind).toBe('asset'); expect(param.kind === 'asset' && param.assetTypes).toBeUndefined(); } }); });