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>
135 lines
4.8 KiB
TypeScript
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();
|
|
}
|
|
});
|
|
});
|