161 lines
5.7 KiB
TypeScript
161 lines
5.7 KiB
TypeScript
import { afterEach, describe, expect, it } from 'vitest';
|
||
import { CreateAssetCommand, CreateTextureCommand } from '../../src/core/commands/assetCommands';
|
||
import type { Asset } from '../../src/core/model/Asset';
|
||
import type { PropertyBag } from '../../src/core/model/Properties';
|
||
import {
|
||
generatedKey,
|
||
generatedPictureOf,
|
||
generatesPictures,
|
||
isGeneratedFile,
|
||
parseGeneratedKey,
|
||
registerPictureSource,
|
||
} from '../../src/core/model/generated';
|
||
import { findAsset } from '../../src/core/model/Project';
|
||
import { resolvePicture } from '../../src/core/model/texture';
|
||
import { spriteFromTexture } from '../../src/core/properties/aspect';
|
||
import { pictureChoices, textureRefLabel } from '../../src/core/properties/textureRef';
|
||
import { makeSpriteScene } from '../helpers';
|
||
|
||
const FILE = `${'ab'.repeat(32)}.png`;
|
||
|
||
/**
|
||
* Stands in for the first real generator (#129): an asset type outside the closed list, which is
|
||
* also what a plugin would bring. The drawing itself is beside the point here – what is measured
|
||
* is the path a canvas size and a revision take through the model.
|
||
*/
|
||
interface DrawnAsset {
|
||
id: string;
|
||
type: string;
|
||
name: string;
|
||
props: PropertyBag;
|
||
canvas: { w: number; h: number };
|
||
revision: number;
|
||
}
|
||
|
||
let unregister: (() => void) | null = null;
|
||
|
||
function register(): void {
|
||
unregister?.();
|
||
unregister = registerPictureSource('drawing', (asset) => {
|
||
const drawn = asset as unknown as DrawnAsset;
|
||
return { width: drawn.canvas.w, height: drawn.canvas.h, revision: drawn.revision };
|
||
});
|
||
}
|
||
|
||
afterEach(() => {
|
||
unregister?.();
|
||
unregister = null;
|
||
});
|
||
|
||
function sceneWithDrawing(name = 'Logo') {
|
||
const fixture = makeSpriteScene();
|
||
const drawing: DrawnAsset = {
|
||
id: 'draw-1',
|
||
type: 'drawing',
|
||
name,
|
||
props: {},
|
||
canvas: { w: 256, h: 128 },
|
||
revision: 1,
|
||
};
|
||
fixture.project.assets.push(drawing as unknown as Asset);
|
||
register();
|
||
return { ...fixture, drawing };
|
||
}
|
||
|
||
describe('the key a drawn picture is named by', () => {
|
||
it('survives being built and taken apart again', () => {
|
||
expect(generatedKey('a1', 7)).toBe('gen:a1@7');
|
||
expect(parseGeneratedKey('gen:a1@7')).toEqual({ assetId: 'a1', revision: '7' });
|
||
// A fingerprint is a revision as good as a counter – it cannot be forgotten in a command.
|
||
expect(parseGeneratedKey(generatedKey('a1', 'c0ffee'))).toEqual({ assetId: 'a1', revision: 'c0ffee' });
|
||
});
|
||
|
||
it('is nothing an uploaded file could be mistaken for', () => {
|
||
expect(isGeneratedFile(FILE)).toBe(false);
|
||
expect(isGeneratedFile('')).toBe(false);
|
||
expect(isGeneratedFile('gen:a1')).toBe(false);
|
||
expect(isGeneratedFile('gen:@7')).toBe(false);
|
||
expect(isGeneratedFile('gen:a1@')).toBe(false);
|
||
expect(isGeneratedFile('a1@7')).toBe(false);
|
||
});
|
||
});
|
||
|
||
describe('an asset that draws its own picture', () => {
|
||
it('is no picture at all while nothing is registered for its type', () => {
|
||
const f = sceneWithDrawing();
|
||
unregister?.();
|
||
unregister = null;
|
||
expect(generatesPictures('drawing')).toBe(false);
|
||
expect(generatedPictureOf(f.drawing as unknown as Asset)).toBeNull();
|
||
expect(resolvePicture(f.project, f.drawing.id)).toBeNull();
|
||
expect(textureRefLabel(f.project, f.drawing.id)).toBe(`${f.drawing.id}?`);
|
||
});
|
||
|
||
it('resolves to its canvas, under the key of its revision', () => {
|
||
const f = sceneWithDrawing();
|
||
expect(resolvePicture(f.project, f.drawing.id)).toEqual({
|
||
file: 'gen:draw-1@1',
|
||
smooth: true,
|
||
rect: null,
|
||
sourceWidth: 256,
|
||
sourceHeight: 128,
|
||
slice: null,
|
||
});
|
||
});
|
||
|
||
it('reads as a new picture once the drawing has moved on', () => {
|
||
const f = sceneWithDrawing();
|
||
const before = resolvePicture(f.project, f.drawing.id)?.file;
|
||
f.drawing.revision = 2;
|
||
f.drawing.canvas = { w: 64, h: 64 };
|
||
const after = resolvePicture(f.project, f.drawing.id);
|
||
expect(after?.file).not.toBe(before);
|
||
expect(after?.file).toBe('gen:draw-1@2');
|
||
expect([after?.sourceWidth, after?.sourceHeight]).toEqual([64, 64]);
|
||
});
|
||
|
||
it('has nothing to show while its canvas has no size', () => {
|
||
const f = sceneWithDrawing();
|
||
f.drawing.canvas = { w: 0, h: 128 };
|
||
expect(resolvePicture(f.project, f.drawing.id)).toBeNull();
|
||
});
|
||
|
||
it('takes the filter it is worn with, like an uploaded picture', () => {
|
||
const f = sceneWithDrawing();
|
||
expect(resolvePicture(f.project, f.drawing.id)?.smooth).toBe(true);
|
||
f.drawing.props.filter = 'nearest';
|
||
expect(resolvePicture(f.project, f.drawing.id)?.smooth).toBe(false);
|
||
});
|
||
|
||
it('measures a new sprite, as an uploaded picture does', () => {
|
||
const f = sceneWithDrawing();
|
||
expect(spriteFromTexture(f.project, f.drawing.id)).toEqual({
|
||
texture: f.drawing.id,
|
||
width: 256,
|
||
height: 128,
|
||
});
|
||
});
|
||
|
||
it('stands among the textures in a texture field, by name and without frames', () => {
|
||
const f = sceneWithDrawing('Alpha');
|
||
const texture = new CreateTextureCommand('Bricks', { file: FILE, width: 64, height: 48 });
|
||
f.stack.execute(texture);
|
||
texture.asset.frames.push({ id: 'f1', name: 'Idle', x: 0, y: 0, w: 32, h: 24 });
|
||
f.stack.execute(new CreateAssetCommand('animation', 'Walk'));
|
||
|
||
expect(pictureChoices(f.project).map((c) => [c.id, c.label])).toEqual([
|
||
[f.drawing.id, 'Alpha'],
|
||
[texture.asset.id, 'Bricks'],
|
||
['f1', 'Bricks / Idle'],
|
||
]);
|
||
expect(textureRefLabel(f.project, f.drawing.id)).toBe('Alpha');
|
||
});
|
||
|
||
it('is worn by a sprite through the same property an uploaded picture is', () => {
|
||
const f = sceneWithDrawing();
|
||
const sprite = findAsset(f.project, f.spriteId)!;
|
||
sprite.props.texture = f.drawing.id;
|
||
expect(resolvePicture(f.project, String(sprite.props.texture))?.file).toBe('gen:draw-1@1');
|
||
});
|
||
});
|