Files
feuerwerk/tools/e2e/15-aspect.mjs
T
rootandClaude Opus 5 9d93020bd8 Add a Container tab and propagate template edits
The Container tab reuses SceneViewport against any object holder, so a
container template is edited exactly like a scene. Right-click no longer
changes the selection, which previously made nesting impossible: opening
the menu on a container loaded it, and "Place in container" then targeted
itself.

Template edits (move, rename, override) now reach already-placed instances
per slot, through a shared templateSync used by both the apply path and the
plain object commands. Copies the user has touched keep their own value.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 20:19:54 +02:00

114 lines
5.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* The ↔ / ↕ buttons beside a sprite's Width and Height: fit that side to the proportions of the
* picture the sprite wears, and not exist at all while it wears none.
*
* Runs on the sheet the texture steps imported (64 × 48) and adds a frame with a deliberately
* different shape, which is what makes "sheet or frame?" a question the numbers can answer.
*/
/** The rows are read as numbers, so a float from a division needs a tolerance. */
const near = (value, expected) => Math.abs(value - expected) < 0.01;
const spriteProp = (key) => `__gm.state.project.assets.find((a) => a.id === 'tex-sprite').props.${key}`;
export async function aspect(t) {
const { check, sleep, json, evaluate, shot, clickText, press, undo, mutate, select } = t;
await press('Escape');
await evaluate('document.activeElement instanceof HTMLElement && document.activeElement.blur()');
const sheet = await json("(__gm.state.project.assets.find((a) => a.type === 'texture') ?? {}).id ?? ''");
check('the imported sheet is still there', sheet !== '', sheet);
// 26. A sprite wearing nothing has nothing to be proportional to, so the buttons are absent.
// Edited as an asset: one base value per row instead of an override, so the numbers below
// say what they mean.
await mutate(`
const props = __gm.state.project.assets.find((a) => a.id === 'tex-sprite').props;
props.texture = '';
props.width = 100;
props.height = 100`);
await select({ kind: 'asset', assetId: 'tex-sprite' });
await sleep(600);
check('the sprite asset is in the inspector', Boolean(await json("__gm.locate('Width')")));
check(
'no texture, no aspect buttons',
(await json("__gm.locate('↔')")) === null && (await json("__gm.locate('↕')")) === null,
);
await shot('40-aspect-without-a-texture');
// 27. Wearing the 64 x 48 sheet, a 100 x 100 sprite is out of proportion – both appear.
await mutate(`${spriteProp('texture')} = ${JSON.stringify(sheet)}`);
await sleep(500);
check(
'assigning a texture brings both buttons out',
Boolean(await json("__gm.locate('↔')")) && Boolean(await json("__gm.locate('↕')")),
);
await shot('41-aspect-buttons');
// 28. ↔ moves the width only, to the sheet's ratio: 100 x 64/48.
await clickText('↔');
await sleep(500);
const fitted = { w: await json(spriteProp('width')), h: await json(spriteProp('height')) };
check(
'↔ sets the width from the height and the sheet ratio',
near(fitted.w, (100 * 64) / 48) && fitted.h === 100,
JSON.stringify(fitted),
);
check(
'and both buttons go once the sprite is proportional',
(await json("__gm.locate('↔')")) === null && (await json("__gm.locate('↕')")) === null,
);
await shot('42-aspect-fitted');
// 28a. One command, so it is one undo step – and the buttons come back with the old shape.
await undo();
await sleep(500);
check('undo takes the width back', (await json(spriteProp('width'))) === 100);
check('and the buttons return with it', Boolean(await json("__gm.locate('↔')")));
await t.redo();
await sleep(500);
// 29. A frame is the picture the sprite wears, not the sheet it was cut from: 40 x 10 against
// the sheet's 64 x 48, so the two ratios cannot be confused for one another.
await mutate(`
const asset = __gm.state.project.assets.find((a) => a.id === ${JSON.stringify(sheet)});
asset.frames.push({ id: 'aspect-frame', name: 'Wide', x: 0, y: 0, w: 40, h: 10 });
${spriteProp('texture')} = 'aspect-frame'`);
await sleep(500);
check('a frame of another shape brings the buttons back', Boolean(await json("__gm.locate('↕')")));
const before = await json(spriteProp('width'));
await clickText('↕');
await sleep(500);
const framed = { w: await json(spriteProp('width')), h: await json(spriteProp('height')) };
check(
'↕ sets the height from the frame ratio, not the sheet it belongs to',
near(framed.h, (before * 10) / 40) && framed.w === before,
JSON.stringify({ framed, sheetWouldGive: (before * 48) / 64 }),
);
check(
'and the buttons go again',
(await json("__gm.locate('↔')")) === null && (await json("__gm.locate('↕')")) === null,
);
// 30. The same rows on a placed object write an override rather than the asset value.
await mutate(`${spriteProp('texture')} = ${JSON.stringify(sheet)}`);
await select({ kind: 'object', holderId: 'scene1', objectIds: ['otex'], objectId: 'otex' });
await sleep(600);
await mutate("__gm.state.activeScene.objects.find((o) => o.id === 'otex').overrides.height = 90");
await sleep(400);
check('the object row offers the fit as well', Boolean(await json("__gm.locate('↔')")));
// The crowded case: an overridden, out-of-proportion row carries ↕ next to ↑ and ↺.
check('and shows it beside the override buttons', (await json('__gm.textOverflows()')).length === 0);
await shot('43-aspect-beside-the-override-buttons');
await clickText('↔');
await sleep(500);
const override = await json("__gm.state.activeScene.objects.find((o) => o.id === 'otex').overrides");
check(
'fitting a scene object writes an override, leaving the asset alone',
near(override.width, (90 * 64) / 48) && near(await json(spriteProp('width')), framed.w),
JSON.stringify({ override, asset: await json(spriteProp('width')) }),
);
await shot('44-aspect-on-an-object');
}