From d2701b3ec5cb42597dd81eb8930b86113d53283d Mon Sep 17 00:00:00 2001 From: MelekTaus Date: Mon, 28 Sep 2026 19:27:55 +0200 Subject: [PATCH] Scroll the image tile into view before clicking it in e2e 12 The image picker scrolls once the texture store holds more images than fit into the dialog. The hook imagePickerTile still reports the tile position then, so the click in module 12 landed outside the visible list and picked nothing. Locally spare.png sat at place 27 of 32, and the full run broke off in module 12 with "Texture not found". - FilePicker exposes its scroll area as listView. - The new test hook __gm.imagePickerRect() returns that area. - Both tile clicks in module 12 wheel the tile into view first with the existing inView helper, as modules 41 and 43 do. The editor behaves the same. Only the hook is new. The texture store is shared with real projects, so the seed does not clean it up. Tests: - ./check.sh: 188 files, 2746 tests, CHECK EXIT 0 - GM_STEPS=10,12: ALL PASSED, 58 checks - Full local run: ALL PASSED, 1535 checks - Counter-check: the old module 12 against the same bundle gives 6 FAILED (selected: null, then "Texture not found") Co-Authored-By: Claude Opus 5.5 --- src/editor/FilePicker.ts | 5 +++++ src/editor/debug/uiHooks.ts | 4 ++++ tools/e2e/12-textures.mjs | 9 +++++++-- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/editor/FilePicker.ts b/src/editor/FilePicker.ts index 80b0d173..3e776a15 100644 --- a/src/editor/FilePicker.ts +++ b/src/editor/FilePicker.ts @@ -272,6 +272,11 @@ export abstract class FilePicker extends Popup { }; } + /** The scrolled part of the dialog – what tells an entry in view from one past its edge (test hook). */ + get listView(): ScrollContainer { + return this.scroll; + } + override close(): void { super.close(); if (current === this) current = null; diff --git a/src/editor/debug/uiHooks.ts b/src/editor/debug/uiHooks.ts index c9c2b126..84babe86 100644 --- a/src/editor/debug/uiHooks.ts +++ b/src/editor/debug/uiHooks.ts @@ -283,6 +283,10 @@ export function uiHooks({ app, state, editor }: HookContext) { /** The open image picker: what it offers, what is picked, and where a tile sits. */ imagePicker: () => currentImagePicker()?.debugInfo() ?? null, imagePickerTile: (file: string) => currentImagePicker()?.tileGlobal(file) ?? null, + imagePickerRect: () => { + const picker = currentImagePicker(); + return picker ? rectOf(picker.listView) : null; + }, /** The same for the font picker, whose rows are the preview. */ fontPicker: () => currentFontPicker()?.debugInfo() ?? null, fontPickerRow: (file: string) => currentFontPicker()?.rowGlobal(file) ?? null, diff --git a/tools/e2e/12-textures.mjs b/tools/e2e/12-textures.mjs index d0fdb1e6..cb06c67a 100644 --- a/tools/e2e/12-textures.mjs +++ b/tools/e2e/12-textures.mjs @@ -1,4 +1,5 @@ import { colourDistance } from '../lib/harness.mjs'; +import { inView } from './lib/scaffold.mjs'; /** * Uploading a picture and wearing it. Runs last because it adds an asset and repaints the @@ -363,7 +364,11 @@ export async function textures(t) { ); check('building the tiles draws no complaint from Pixi', tileComplaints.length === 0, tileComplaints); - const tile = await json(`__gm.imagePickerTile(${JSON.stringify(spare)})`); + // Scrolled into view first: the server holds every picture ever uploaded, so on a long-lived + // one the spare's tile sits below the dialog's fold, where a click lands on nothing. + const tileOf = (file) => + inView(t, '__gm.imagePickerRect()', `__gm.imagePickerTile(${JSON.stringify(file)})`); + const tile = await tileOf(spare); await click(tile.x, tile.y); check( 'clicking a tile picks that image', @@ -424,7 +429,7 @@ export async function textures(t) { (reopened?.used ?? []).includes(spare), JSON.stringify(reopened?.used), ); - const sameTile = await json(`__gm.imagePickerTile(${JSON.stringify(spare)})`); + const sameTile = await tileOf(spare); await doubleClick(sameTile.x, sameTile.y); await sleep(600); const afterSecondPick = await json(