Files
feuerwerk/tests/core/asset-ref.test.ts
T
rootandClaude Opus 5 4d84231b9f Trim needless comments and fix the picker width
Remove comments that only restate the code, keeping the section banners
and the notes that carry a reason, a measured fact or an external
contract.

ButtonOptions.width claimed to be a fixed width but only set minWidth,
so the Graph and Texture pickers grew with the name they showed. It is
now what it promises, with a separate minWidth for the buttons that have
two forms. Narrow buttons shrink their padding, otherwise a 24px icon
button had 8px left for its glyph and truncate dropped it entirely.

__gm.labelsDropped() guards that class: the overflow guard only saw text
that was too wide, never a label truncated to nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 15:52:05 +02:00

135 lines
4.8 KiB
TypeScript

/**
* 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<AssetType, string> } {
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();
}
});
});