Add a decimals option, fix dropdown overflow and the texture filter
- NumberField takes a decimals option (default 3); frame rects and the slice dialog use 0, matching their integer model. - Button truncates its label to the drawn width; Dropdown reserves the chevron slot. NodeView reuses the shared truncate(). - Setting scaleMode leaves the style's shared resource id in place, so the old GPU sampler stayed in use until a reload. Call style.update(). - Graph gets a header bar like the Texture tab: GraphEditor is now the frame, the former editor is GraphCanvas. A selection the tab does not concern no longer unloads the behavior. - New e2e step 14 guards text overflow across all widgets. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
35a1a23af8
commit
f16801aef3
22 files changed
+874
-493
No files matched your search
+29
-4
@@ -186,15 +186,40 @@ export async function graph(t) {
|
||||
await undo();
|
||||
check('undo removes the variable', (await variableKeys()) === 'speed', await variableKeys());
|
||||
|
||||
// 13h. The graph strictly mirrors the selection: nothing / a non-behavior → empty editor.
|
||||
// 13h. The tab keeps its subject. A selection about something else (nothing, a camera) leaves
|
||||
// the graph alone – clicking a sprite to look at it must not empty the editor you are
|
||||
// working in – and the header picker switches behaviors without any selection at all.
|
||||
const size = await graphCounts();
|
||||
const readout = `${size.nodes} nodes · ${size.edges} links`;
|
||||
check(
|
||||
'the header reports the size of the shown graph',
|
||||
Boolean(await evaluate(`__gm.locate(${JSON.stringify(readout)})`)),
|
||||
readout,
|
||||
);
|
||||
await select(null);
|
||||
await sleep(100);
|
||||
check('graph editor empties when nothing is selected', (await evaluate(`__gm.graph.current()`)) === null);
|
||||
check(
|
||||
'graph editor keeps its behavior when nothing is selected',
|
||||
(await evaluate(`__gm.graph.current()`)) === 'mover',
|
||||
);
|
||||
await select({ kind: 'asset', assetId: 'cam' });
|
||||
await sleep(100);
|
||||
check(
|
||||
'graph editor stays empty for a non-behavior asset',
|
||||
(await evaluate(`__gm.graph.current()`)) === null,
|
||||
'graph editor keeps its behavior for a non-behavior asset',
|
||||
(await evaluate(`__gm.graph.current()`)) === 'mover',
|
||||
);
|
||||
// The picker is the *last* "Mover" on screen: the assets panel shows one too, and the
|
||||
// inspector is on the camera.
|
||||
const pickerAt = await locate('Mover', true);
|
||||
await click(pickerAt.x, pickerAt.y);
|
||||
await sleep(150);
|
||||
const spawnerRow = await locate('Spawner', true);
|
||||
await click(spawnerRow.x, spawnerRow.y);
|
||||
await sleep(200);
|
||||
check(
|
||||
'the header picker switches the graph to another behavior',
|
||||
(await evaluate(`__gm.graph.current()`)) === 'spawner',
|
||||
String(await evaluate(`__gm.graph.current()`)),
|
||||
);
|
||||
await select({ kind: 'asset', assetId: 'mover' });
|
||||
await sleep(100);
|
||||
|
||||
@@ -138,6 +138,56 @@ export async function textures(t) {
|
||||
);
|
||||
await shot('32-textured-sprite');
|
||||
|
||||
// 19b-i. Switching the Filter property takes effect at once, without a reload.
|
||||
//
|
||||
// Read off the screen rather than off `scaleMode`, because the property was always assigned
|
||||
// correctly – what used to survive the change was the GPU sampler behind it. The measurement
|
||||
// is what "smooth" means: across the red/green border, nearest sampling shows only the two
|
||||
// pure colours, linear sampling blends them into something that is neither.
|
||||
const enlarge = (w, h) => `
|
||||
const sprite = __gm.state.project.assets.find((a) => a.id === 'tex-sprite');
|
||||
sprite.props.width = ${w};
|
||||
sprite.props.height = ${h}`;
|
||||
await mutate(enlarge(320, 240));
|
||||
await sleep(600);
|
||||
const wide = await json("__gm.objectRect('otex')");
|
||||
// Sampled in the upper half so the line crosses only the red/green border, not the corner
|
||||
// where all four quadrants meet.
|
||||
const acrossBorder = async () => {
|
||||
const y = Math.round(wide.y - wide.h * 0.25);
|
||||
const points = [];
|
||||
for (let dx = -2; dx <= 2; dx++) {
|
||||
points.push(await json(`__gm.pixelAt(${Math.round(wide.x + dx)}, ${y})`));
|
||||
}
|
||||
return points;
|
||||
};
|
||||
const setFilter = (value) =>
|
||||
mutate(
|
||||
`__gm.state.project.assets.find((a) => a.id === ${JSON.stringify(texture.id)}).props.filter = ${JSON.stringify(value)}`,
|
||||
);
|
||||
|
||||
await setFilter('nearest');
|
||||
await sleep(600);
|
||||
const hard = await acrossBorder();
|
||||
const pure = (c) => colourDistance(c, '#e63c3c') < 60 || colourDistance(c, '#3cc85a') < 60;
|
||||
check(
|
||||
'pixel-art filtering keeps the border between two quadrants hard',
|
||||
hard.every(pure),
|
||||
JSON.stringify(hard),
|
||||
);
|
||||
|
||||
await setFilter('linear');
|
||||
await sleep(600);
|
||||
const soft = await acrossBorder();
|
||||
check(
|
||||
'switching the filter back to smooth blends the border without a reload',
|
||||
soft.some((c) => !pure(c)),
|
||||
JSON.stringify({ nearest: hard, linear: soft }),
|
||||
);
|
||||
await shot('33-texture-filter-smooth');
|
||||
await mutate(enlarge(160, 120));
|
||||
await sleep(400);
|
||||
|
||||
// 19c. The colour multiplies the picture, so a tinted white quadrant stops being white.
|
||||
await mutate("__gm.state.project.assets.find((a) => a.id === 'tex-sprite').props.color = 0x3366ff");
|
||||
await sleep(600);
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Labels must be shortened to fit their control, not written past it. Runs last because it
|
||||
* renames assets to absurd lengths to provoke the case.
|
||||
*
|
||||
* `__gm.textOverflows()` is a general guard rather than a check on one widget: it walks every
|
||||
* visible text and reports the ones drawn wider than the widget that owns them. It was verified
|
||||
* to actually catch this class of bug – against a build without the fix it reported the graph
|
||||
* picker at 106 px over, and nothing else.
|
||||
*/
|
||||
|
||||
/** A name far wider than any 200 px picker, so a control that does not truncate must spill. */
|
||||
const LONG_NAME = 'Player movement with jumping and coyote time';
|
||||
|
||||
export async function overflow(t) {
|
||||
const { check, sleep, json, evaluate, shot, mutate } = t;
|
||||
|
||||
await mutate(`
|
||||
for (const asset of __gm.state.project.assets) {
|
||||
if (asset.type === 'behavior' || asset.type === 'texture') asset.name = ${JSON.stringify(LONG_NAME)};
|
||||
}
|
||||
__gm.state.select(null)`);
|
||||
await sleep(600);
|
||||
|
||||
// 21. Every tab, because each one owns a different set of controls: the two header bars with
|
||||
// their pickers, the inspector's dropdowns, and the toolbar.
|
||||
for (const tab of ['scene', 'graph', 'texture', 'game']) {
|
||||
await evaluate(`__gm.showTab(${JSON.stringify(tab)})`);
|
||||
await sleep(400);
|
||||
const spills = await json('__gm.textOverflows()');
|
||||
check(
|
||||
`no label is drawn past its control in the ${tab} tab`,
|
||||
spills.length === 0,
|
||||
JSON.stringify(spills),
|
||||
);
|
||||
}
|
||||
|
||||
// 21a. Shortened, not simply dropped: the value must still be readable and marked as cut.
|
||||
await evaluate("__gm.showTab('graph')");
|
||||
await sleep(400);
|
||||
const shown = (await json('__gm.textOverflows(true)')).find((e) => e.text.startsWith('Player movement'));
|
||||
check(
|
||||
'a value too wide for its dropdown is cut with an ellipsis',
|
||||
Boolean(shown) && shown.text.endsWith('…') && shown.text.length > 8,
|
||||
JSON.stringify(shown),
|
||||
);
|
||||
await shot('36-long-labels');
|
||||
|
||||
// 21b. An inspector full of dropdowns – the parent row and the enum fields – with a selection
|
||||
// whose asset name is now overlong too.
|
||||
await evaluate("__gm.showTab('scene')");
|
||||
await sleep(300);
|
||||
await evaluate("__gm.state.select({ kind: 'object', objectId: 'o1' })");
|
||||
await sleep(500);
|
||||
const inspector = await json('__gm.textOverflows()');
|
||||
check('no label spills in the inspector', inspector.length === 0, JSON.stringify(inspector));
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import { textAndEnabled } from './10-text-and-enabled.mjs';
|
||||
import { menus } from './11-menus.mjs';
|
||||
import { textures } from './12-textures.mjs';
|
||||
import { frames } from './13-frames.mjs';
|
||||
import { overflow } from './14-overflow.mjs';
|
||||
|
||||
export const STEPS = [
|
||||
hierarchy,
|
||||
@@ -31,4 +32,5 @@ export const STEPS = [
|
||||
menus,
|
||||
textures,
|
||||
frames,
|
||||
overflow,
|
||||
];
|
||||
Reference in new issue
Block a user