A sprite's texture and shader were only reachable by hunting for them
in the asset panel. Every place that shows such a reference now leads
straight to it.
- Object menu (hierarchy and scene view): "Open texture" and "Open
shader" sit right below "Open asset" when the sprite has one. The
object's own values count, so an overridden shader opens the
override.
- Asset context menu: the same entries at the top.
- Inspector: every asset field with a value shows its label as a blue,
underlined link. One click selects the asset, a double click opens
it. An override stays gold and gets the underline too.
- The first click rebuilds the inspector, so the second one lands on a
different row. The inspector catches that second click before any
row sees it.
- New pure rule `references.ts`: the asset behind a reference field. A
frame leads to its sheet. Kinds without a tab (palette, font, sound)
get no menu entry.
- `followsPress` in `clicks.ts` keeps the double-click window and
tolerance in one place.
- `activateAsset` in `actions.ts`: opening means the same everywhere
as a double click in the asset panel, so a scene also becomes the
active one. The asset panel uses it too.
Tests:
- ./check.sh: 150 files, 2226 tests, CHECK EXIT 0 (before 148/2214).
- 12 new unit tests: 6 for the reference rule, 4 for the link in
createFieldRow, 2 for followsPress.
- 5 new e2e checks in 28l-ii: both menus, the link's look, click
selects, double click opens.
- e2e 1-16,26,28,35,39,40 on the final build: ALL PASSED, 732 checks.
- Counter-probes: no "Open ..." entries, no underline and a second
click never recognised each fail exactly at their checks.
- Check total 1341 (CI counted 1336 at fd6af84), computed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
90 lines
3.5 KiB
TypeScript
90 lines
3.5 KiB
TypeScript
/** Which asset a reference field points at – what "Open texture" and the inspector link open (#221). */
|
||
import { describe, expect, it } from 'vitest';
|
||
import { CreateAssetCommand, CreateTextureCommand } from '../../src/core/commands/assetCommands';
|
||
import { AddFrameCommand } from '../../src/core/commands/frameCommands';
|
||
import type { PixelArtAsset } from '../../src/core/model/Asset';
|
||
import { findAsset } from '../../src/core/model/Project';
|
||
import { type PropertyDef, schemaForAsset } from '../../src/core/properties/PropertySchema';
|
||
import { referencedAssetId, referencesOf } from '../../src/core/properties/references';
|
||
import { resolveAssetProps } from '../../src/core/properties/resolve';
|
||
import { SHADER_KEY } from '../../src/core/properties/shaders';
|
||
import { makeSpriteScene } from '../helpers';
|
||
|
||
const FILE = `${'ab'.repeat(32)}.png`;
|
||
const TEXTURE: PropertyDef = { key: 'texture', label: 'Texture', kind: 'texture', default: '' };
|
||
const SHADER: PropertyDef = {
|
||
key: SHADER_KEY,
|
||
label: 'Shader',
|
||
kind: 'asset',
|
||
default: '',
|
||
assetTypes: ['shader'],
|
||
};
|
||
|
||
function setup() {
|
||
const fixture = makeSpriteScene();
|
||
const texture = new CreateTextureCommand('Sheet', { file: FILE, width: 64, height: 32 });
|
||
fixture.stack.execute(texture);
|
||
const frame = new AddFrameCommand(texture.asset.id, { x: 0, y: 0, w: 16, h: 16 }, 'Idle');
|
||
fixture.stack.execute(frame);
|
||
const shader = new CreateAssetCommand('shader', 'Glow');
|
||
fixture.stack.execute(shader);
|
||
const art = new CreateAssetCommand('pixelart', 'Art');
|
||
fixture.stack.execute(art);
|
||
return {
|
||
...fixture,
|
||
textureId: texture.asset.id,
|
||
frameId: frame.frameId,
|
||
shaderId: shader.asset.id,
|
||
art: art.asset as PixelArtAsset,
|
||
};
|
||
}
|
||
|
||
describe('referencedAssetId', () => {
|
||
it('is the asset a reference names', () => {
|
||
const f = setup();
|
||
expect(referencedAssetId(f.project, TEXTURE, f.textureId)).toBe(f.textureId);
|
||
expect(referencedAssetId(f.project, SHADER, f.shaderId)).toBe(f.shaderId);
|
||
});
|
||
|
||
it('is the sheet for a frame of a texture or of pixel art', () => {
|
||
const f = setup();
|
||
expect(referencedAssetId(f.project, TEXTURE, f.frameId)).toBe(f.textureId);
|
||
f.art.frames.push({ id: 'second', cels: {} });
|
||
expect(referencedAssetId(f.project, TEXTURE, 'second')).toBe(f.art.id);
|
||
});
|
||
|
||
it('is nothing for an empty or dangling reference', () => {
|
||
const f = setup();
|
||
expect(referencedAssetId(f.project, TEXTURE, '')).toBeNull();
|
||
expect(referencedAssetId(f.project, SHADER, 'gone')).toBeNull();
|
||
});
|
||
|
||
it('only resolves frames in a texture field', () => {
|
||
const f = setup();
|
||
expect(referencedAssetId(f.project, SHADER, f.frameId)).toBeNull();
|
||
});
|
||
|
||
it('is nothing for a field that names no asset, whatever it holds', () => {
|
||
const f = setup();
|
||
const text: PropertyDef = { key: 'name', label: 'Name', kind: 'text', default: '' };
|
||
expect(referencedAssetId(f.project, text, f.textureId)).toBeNull();
|
||
});
|
||
});
|
||
|
||
describe('referencesOf', () => {
|
||
it("lists a sprite asset's texture and shader in schema order, and nothing unset", () => {
|
||
const f = setup();
|
||
const sprite = findAsset(f.project, f.spriteId)!;
|
||
const refs = () =>
|
||
referencesOf(f.project, schemaForAsset(f.project, sprite), resolveAssetProps(f.project, sprite));
|
||
sprite.props.texture = '';
|
||
expect(refs()).toEqual([]);
|
||
sprite.props[SHADER_KEY] = f.shaderId;
|
||
sprite.props.texture = f.frameId;
|
||
expect(refs()).toEqual([
|
||
{ label: 'Texture', assetId: f.textureId },
|
||
{ label: 'Shader', assetId: f.shaderId },
|
||
]);
|
||
});
|
||
});
|