Create a sprite asset from a texture's context menu

The sprite takes the whole image at its pixel size and the texture's name,
numbered like any other clash. CreateAssetCommand now accepts starting
properties; they go through the schema in execute, so an unknown key or a
bad value is refused before the project is touched, and one undo removes
the asset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Opus 5 committed 2026-09-11 09:57:19 +02:00
1 parent 1e2285c545
commit 9212e61c4c
9 files changed
+137 -7

No files matched your search

+17
View File
@@ -6,6 +6,7 @@ import {
fitAxisToAspect,
matchesAspect,
matchesPixelSize,
spriteFromTexture,
texturePixelSize,
} from '../../src/core/properties/aspect';
import { makeSpriteScene } from '../helpers';
@@ -97,3 +98,19 @@ describe('matchesAspect', () => {
expect(matchesAspect(size, props({ width: Number.NaN, height: 10 }))).toBe(false);
});
});
describe('spriteFromTexture', () => {
it('wears the texture at its pixel size', () => {
const sheet = makeSheet();
expect(spriteFromTexture(sheet.project, sheet.textureId)).toEqual({
texture: sheet.textureId,
width: 64,
height: 48,
});
});
it('leaves the size to the defaults while the image has not been measured', () => {
const sheet = makeSheet(0, 0);
expect(spriteFromTexture(sheet.project, sheet.textureId)).toEqual({ texture: sheet.textureId });
});
});
+37 -1
View File
@@ -1,8 +1,9 @@
import { describe, expect, it } from 'vitest';
import { CreateTextureCommand } from '../../src/core/commands/assetCommands';
import { CreateAssetCommand, CreateTextureCommand } from '../../src/core/commands/assetCommands';
import { CommandError } from '../../src/core/commands/Command';
import { isTextureAsset } from '../../src/core/model/Asset';
import { findAsset } from '../../src/core/model/Project';
import { spriteFromTexture } from '../../src/core/properties/aspect';
import { makeProject } from '../helpers';
const FILE = `${'b'.repeat(64)}.webp`;
@@ -40,3 +41,38 @@ describe('CreateTextureCommand', () => {
expect(new Set(project.assets.map((a) => a.id)).size).toBe(2);
});
});
describe('CreateAssetCommand with starting props', () => {
it('creates a sprite wearing the texture at its pixel size, and removes it again', () => {
const { project, stack } = makeProject();
const texture = new CreateTextureCommand('Hero', { file: FILE, width: 64, height: 32 });
stack.execute(texture);
const sprite = new CreateAssetCommand('sprite', 'Hero 2', spriteFromTexture(project, texture.asset.id));
stack.execute(sprite);
expect(findAsset(project, sprite.asset.id)?.props).toEqual({
enabled: true,
width: 64,
height: 32,
color: 0xffffff,
texture: texture.asset.id,
});
stack.undo();
expect(findAsset(project, sprite.asset.id)).toBeUndefined();
stack.redo();
expect(findAsset(project, sprite.asset.id)?.props.texture).toBe(texture.asset.id);
});
it('passes each value through the schema', () => {
const { project, stack } = makeProject();
const sprite = new CreateAssetCommand('sprite', 'Tiny', { width: -5 });
stack.execute(sprite);
expect(findAsset(project, sprite.asset.id)?.props.width).toBe(1);
});
it('rejects a property the type does not have, leaving the project untouched', () => {
const { project, stack } = makeProject();
expect(() => stack.execute(new CreateAssetCommand('sprite', 'Odd', { zoom: 2 }))).toThrow(CommandError);
expect(project.assets).toHaveLength(0);
});
});