A double click on a clip row did not open its tab. The first click of the double click had already selected the clip for the inspector, so the second click selected the same thing again and the editor saw no change. Frames of textures and pixel art had no rows at all. - A double click now asks for the tab with openSelection, even when the selection is already right. - Textures with frames and pixel art with more than one frame get a chevron and one row per frame, like sounds and videos. Which rows an asset has is decided in one place, src/core/model/assetParts.ts, used by the panel and the inspector link. - A texture frame shows in the inspector on click and opens the texture with the frame selected on double click. - A pixel art frame selects the pixel art on click, so an open tab paints on that frame. A double click opens the tab on the frame. - A frame can be dragged onto a composition track like a video clip. The item carries the sprite of the whole image, the frame becomes the texture of its object, and it is 3 s long. The agent can do the same with add_composition_item. - A texture label that names a frame now points at the frame row. Found on the way: the e2e check from #425 only double clicked the texture label, where the first click is a ping, so the bug never showed there. Tests: - Unit: asset-parts (new, 6), composition-sprites and references extended, related tests 303/303, agent tests 21/21 - 5 mutations, all red in the matching test - e2e 69 (atx): ALL PASSED, 26 checks, 69t new - Gate (atx): CHECK EXIT 0, 325 files, 5017 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
109 lines
4.6 KiB
TypeScript
109 lines
4.6 KiB
TypeScript
/**
|
||
* The parts of an asset the asset panel lists under it (#425, #430) – clips, frames of a texture and
|
||
* of pixel art – and what a double-click on one does after the click before it has picked it.
|
||
*/
|
||
import { describe, expect, it } from 'vitest';
|
||
import {
|
||
CreateAssetCommand,
|
||
CreateSoundCommand,
|
||
CreateTextureCommand,
|
||
} from '../../src/core/commands/assetCommands';
|
||
import { AddClipCommand } from '../../src/core/commands/clipCommands';
|
||
import { AddFrameCommand } from '../../src/core/commands/frameCommands';
|
||
import type { PixelArtAsset } from '../../src/core/model/Asset';
|
||
import { partOwner, partsOf } from '../../src/core/model/assetParts';
|
||
import { createEmptyProject, findAsset } from '../../src/core/model/Project';
|
||
import { activateAssetOrPart, glancePart } from '../../src/editor/actions';
|
||
import { EditorState } from '../../src/editor/state';
|
||
|
||
function setup() {
|
||
const state = new EditorState();
|
||
state.loadProject(createEmptyProject('p', 'P'), 1);
|
||
const sound = new CreateSoundCommand('Kick', `${'ef'.repeat(32)}.ogg`);
|
||
const texture = new CreateTextureCommand('Sheet', {
|
||
file: `${'ab'.repeat(32)}.png`,
|
||
width: 64,
|
||
height: 32,
|
||
});
|
||
const art = new CreateAssetCommand('pixelart', 'Art');
|
||
for (const c of [sound, texture, art]) expect(state.execute(c)).toBe(true);
|
||
const clip = new AddClipCommand(sound.asset.id, { start: 0.1, end: 0.2 });
|
||
const frame = new AddFrameCommand(texture.asset.id, { x: 0, y: 0, w: 16, h: 16 }, 'Idle');
|
||
for (const c of [clip, frame]) expect(state.execute(c)).toBe(true);
|
||
const events: string[] = [];
|
||
for (const event of ['selection', 'open', 'pixelFrame'] as const) state.on(event, () => events.push(event));
|
||
const pixelArt = () => findAsset(state.project, art.asset.id) as PixelArtAsset;
|
||
return {
|
||
state,
|
||
events,
|
||
soundId: sound.asset.id,
|
||
textureId: texture.asset.id,
|
||
artId: art.asset.id,
|
||
clipId: clip.clipId,
|
||
frameId: frame.frameId,
|
||
pixelArt,
|
||
};
|
||
}
|
||
|
||
describe('the parts of an asset', () => {
|
||
it('are the clips of a sound, the frames of a texture, and those of pixel art from the second on', () => {
|
||
const f = setup();
|
||
const art = f.pixelArt();
|
||
expect(partsOf(findAsset(f.state.project, f.soundId)!)).toEqual([{ id: f.clipId, name: 'Clip 1' }]);
|
||
expect(partsOf(findAsset(f.state.project, f.textureId)!)).toEqual([{ id: f.frameId, name: 'Idle' }]);
|
||
expect(partsOf(art)).toEqual([]);
|
||
expect(partOwner(f.state.project, art.frames[0]!.id)).toBeUndefined();
|
||
art.frames.push({ id: 'second', cels: {} });
|
||
expect(partsOf(art)).toEqual([
|
||
{ id: art.frames[0]!.id, name: 'Frame 1' },
|
||
{ id: 'second', name: 'Frame 2' },
|
||
]);
|
||
});
|
||
|
||
it('name their asset, and an asset or a stranger names none', () => {
|
||
const f = setup();
|
||
expect(partOwner(f.state.project, f.clipId)?.id).toBe(f.soundId);
|
||
expect(partOwner(f.state.project, f.frameId)?.id).toBe(f.textureId);
|
||
expect(partOwner(f.state.project, f.textureId)).toBeUndefined();
|
||
expect(partOwner(f.state.project, '')).toBeUndefined();
|
||
expect(partOwner(f.state.project, 'gone')).toBeUndefined();
|
||
});
|
||
});
|
||
|
||
describe('a double-click on a part (#430)', () => {
|
||
it('opens the tab of a clip the click before picked, which selecting it again would not', () => {
|
||
const f = setup();
|
||
const owner = findAsset(f.state.project, f.soundId)!;
|
||
glancePart(f.state, owner, f.clipId);
|
||
expect(f.state.selection).toEqual({ kind: 'clip', assetId: f.soundId, clipId: f.clipId });
|
||
f.events.length = 0;
|
||
activateAssetOrPart(f.state, f.clipId);
|
||
expect(f.events).toEqual(['open']);
|
||
});
|
||
|
||
it('picks a frame of a texture as the frame, and opens its tab', () => {
|
||
const f = setup();
|
||
glancePart(f.state, findAsset(f.state.project, f.textureId)!, f.frameId);
|
||
expect(f.state.selection).toEqual({ kind: 'frame', assetId: f.textureId, frameId: f.frameId });
|
||
f.events.length = 0;
|
||
activateAssetOrPart(f.state, f.frameId);
|
||
expect(f.events).toEqual(['open']);
|
||
});
|
||
|
||
it('opens pixel art at a frame of it: the asset is selected, the frame shown', () => {
|
||
const f = setup();
|
||
f.pixelArt().frames.push({ id: 'second', cels: {} });
|
||
activateAssetOrPart(f.state, 'second');
|
||
expect(f.state.selection).toEqual({ kind: 'asset', assetId: f.artId });
|
||
expect(f.state.shownPixelFrame).toEqual({ assetId: f.artId, frameId: 'second' });
|
||
expect(f.events).toEqual(['pixelFrame', 'selection', 'open']);
|
||
});
|
||
|
||
it('opens an asset as before', () => {
|
||
const f = setup();
|
||
activateAssetOrPart(f.state, f.textureId);
|
||
expect(f.state.selection).toEqual({ kind: 'asset', assetId: f.textureId });
|
||
expect(f.events).toEqual(['selection', 'open']);
|
||
});
|
||
});
|