test: cover gallery pagination and directory autocomplete deterministically
This commit is contained in:
+2
-1
@@ -11,7 +11,8 @@ original implementation, then run the relevant suites after the fix.
|
||||
| JavaScript | Production extension hooks and callback behavior | Minimal host objects; controlled timers for focused regressions |
|
||||
| Browser | All shipped extension imports and lifecycle registration; real plugin startup, HTTP routes, PNG thumbnails, selection persistence, and image loading | ComfyUI graph/widgets/settings/event interfaces and its saved-image serving endpoint |
|
||||
|
||||
Browser workflows cover swaps, local image browsing/selection, saved paths,
|
||||
Browser workflows cover swaps, local image browsing/selection, pagination,
|
||||
sorting, hidden folders, directory autocomplete, saved paths,
|
||||
denied paths and network recovery, display output, seed persistence/cleanup, and
|
||||
workflow timing, autocomplete insertion, and saved-image previews. They run in Chromium, Firefox, WebKit, and touch Chromium.
|
||||
The harness binds to localhost and writes configuration, selections, and fixture
|
||||
|
||||
+19
-4
@@ -50,14 +50,16 @@ def create_app(root: Path) -> web.Application:
|
||||
loader.CONFIG_FILE = str(root / "config.json")
|
||||
loader.SELECTIONS_FILE = str(root / "selections.json")
|
||||
|
||||
def reset() -> None:
|
||||
def reset(extra_images: int = 0) -> None:
|
||||
(root / "selections.json").unlink(missing_ok=True)
|
||||
shutil.rmtree(root / "output")
|
||||
(root / "output").mkdir()
|
||||
for kind in ("input", "output"):
|
||||
shutil.rmtree(root / kind)
|
||||
(root / kind).mkdir()
|
||||
loader.save_config({"saved_paths": [], "last_path": str(root / "input")})
|
||||
for path, color in [
|
||||
("input/cat & dog.png", "red"),
|
||||
("input/sub/forest.png", "green"),
|
||||
("input/.hidden/hidden.png", "green"),
|
||||
("outside/secret.png", "blue"),
|
||||
]:
|
||||
target = root / path
|
||||
@@ -65,9 +67,22 @@ def create_app(root: Path) -> web.Application:
|
||||
with Image.new("RGB", (16, 12), color) as image:
|
||||
image.save(target)
|
||||
|
||||
for index in range(extra_images):
|
||||
target = root / "input" / "bulk" / f"{index:04}.png"
|
||||
target.parent.mkdir(exist_ok=True)
|
||||
shutil.copyfile(root / "input" / "cat & dog.png", target)
|
||||
|
||||
@routes.post("/__test__/reset")
|
||||
async def reset_route(request: web.Request) -> web.Response:
|
||||
await asyncio.to_thread(reset)
|
||||
data = await request.json() if request.can_read_body else {}
|
||||
extra_images = data.get("extra_images", 0)
|
||||
if (
|
||||
isinstance(extra_images, bool)
|
||||
or not isinstance(extra_images, int)
|
||||
or not 0 <= extra_images <= 60
|
||||
):
|
||||
raise web.HTTPBadRequest()
|
||||
await asyncio.to_thread(reset, extra_images)
|
||||
return web.json_response({"success": True})
|
||||
|
||||
@routes.get("/__test__/state")
|
||||
|
||||
@@ -233,3 +233,53 @@ test('Display Any renders real multiline output and restores serialized values w
|
||||
await expect(page.locator('textarea')).toHaveCount(1);
|
||||
await expect(page.getByLabel('display_0')).toHaveValue('Restored value');
|
||||
});
|
||||
|
||||
test('gallery pagination sorting and hidden-folder controls use real directory pages', async ({ page, request }) => {
|
||||
await expect(await request.post('/__test__/reset', { data: { extra_images: 52 } })).toBeOK();
|
||||
await openHost(page);
|
||||
await createNode(page, 'KikoLocalImageLoader');
|
||||
const folder = name => page.locator('.kiko-folder-name').filter({ hasText: new RegExp(`^${name}$`) });
|
||||
await expect(folder('sub')).toBeVisible();
|
||||
await expect(folder('\\.hidden')).toHaveCount(0);
|
||||
await page.locator('.kiko-hide-dot-folders').uncheck();
|
||||
await expect(folder('\\.hidden')).toBeVisible();
|
||||
await folder('bulk').click();
|
||||
await expect(page.locator('.kiko-card-info-panel')).toHaveCount(50);
|
||||
await expect(page.locator('.kiko-card-info-panel').first()).toHaveText('0000.png');
|
||||
await page.locator('.kiko-sort-order').selectOption('desc');
|
||||
await expect(page.locator('.kiko-card-info-panel').first()).toHaveText('0051.png');
|
||||
await page.locator('.kiko-pagination').getByRole('button', { name: '2', exact: true }).click();
|
||||
await expect(page.locator('.kiko-card-info-panel')).toHaveCount(2);
|
||||
await expect(page.getByAltText('0000.png', { exact: true })).toBeVisible();
|
||||
await page.locator('.kiko-pagination').getByRole('button', { name: '◀', exact: true }).click();
|
||||
await expect(page.locator('.kiko-card-info-panel')).toHaveCount(50);
|
||||
await page.locator('.kiko-pagination').getByRole('button', { name: '▶', exact: true }).click();
|
||||
await expect(page.locator('.kiko-card-info-panel')).toHaveCount(2);
|
||||
});
|
||||
|
||||
test('gallery directory autocomplete handles keyboard selection escape and navigation', async ({ page, request }) => {
|
||||
await openHost(page);
|
||||
await createNode(page, 'KikoLocalImageLoader');
|
||||
await expect(page.getByAltText('cat & dog.png', { exact: true })).toBeVisible();
|
||||
const input = page.locator('.kiko-path-input');
|
||||
const dropdown = page.locator('.kiko-autocomplete-dropdown');
|
||||
const directory = (await state(request)).input;
|
||||
await input.fill(`${directory}/su`);
|
||||
await expect(dropdown).toBeVisible();
|
||||
await expect(dropdown.locator('.folder-name')).toContainText('sub');
|
||||
await input.press('ArrowDown');
|
||||
await expect(dropdown.locator('.selected')).toHaveCount(1);
|
||||
await input.press('ArrowUp');
|
||||
await expect(dropdown.locator('.selected')).toHaveCount(0);
|
||||
await input.press('Escape');
|
||||
await expect(dropdown).toBeHidden();
|
||||
await input.fill(`${directory}/su`);
|
||||
await expect(dropdown).toBeVisible();
|
||||
await input.press('ArrowDown');
|
||||
await input.press('Tab');
|
||||
await expect(input).toBeFocused();
|
||||
await expect(input).toHaveValue(`${directory}/sub/`);
|
||||
await expect(dropdown).toBeHidden();
|
||||
await input.press('Enter');
|
||||
await expect(page.getByAltText('forest.png', { exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -144,3 +144,17 @@ test('gallery HTTP errors preserve actionable server detail and handle non-JSON
|
||||
assert.match(await describe({ status: 403, json: async () => { throw new Error('not JSON'); } }), /allowed folders/);
|
||||
assert.match(await describe({ status: 400, json: async () => ({}) }), /Invalid path/);
|
||||
});
|
||||
|
||||
test('gallery HTTP errors cover null bodies, message details, and generic status fallbacks', async () => {
|
||||
const { context } = load('kiko_local_image_loader.js', { document: { getElementById: () => ({}) } });
|
||||
const describe = vm.runInContext('describeHttpError', context);
|
||||
for (const [status, body, expected] of [
|
||||
[400, { error: 'Invalid directory' }, 'Invalid directory'],
|
||||
[403, { message: 'Not an allowed root' }, 'Not an allowed root'],
|
||||
[400, null, 'Invalid path or request'],
|
||||
[500, { error: 'Storage unavailable' }, 'Storage unavailable'],
|
||||
[404, { message: 'Missing image' }, 'Missing image'],
|
||||
[502, null, 'Request failed (HTTP 502)'],
|
||||
[503, {}, 'Request failed (HTTP 503)'],
|
||||
]) assert.equal(await describe({ status, json: async () => body }), expected);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user