test(thumbnails): cover the prune scope, the Overview notice and the space refusal (#320)
The frontend coverage gate counted the new branches of thumbnail maintenance, the Activity views, the notices' Change location and the api-client's space refusal as uncovered; page and unit tests now drive them.
This commit is contained in:
@@ -136,4 +136,26 @@ def register_thumbnail_location_commands(
|
||||
async def foreign_folder(_request: web.Request) -> web.Response:
|
||||
return web.json_response({"available": await api._run_in_executor(foreign)})
|
||||
|
||||
def unlisted() -> None:
|
||||
"""A source this install no longer lists left its thumbnails here."""
|
||||
setting = preview_store.setting()
|
||||
|
||||
def add(marker: dict) -> None:
|
||||
marker["keys"]["gone-0123abcd"] = {
|
||||
"path": "/gone",
|
||||
"identity": "/gone",
|
||||
"install": setting.install,
|
||||
}
|
||||
|
||||
preview_location.write_marker(setting.path, add)
|
||||
gone = Path(setting.path) / "gone-0123abcd"
|
||||
gone.mkdir()
|
||||
(gone / "x_thumb.webp").write_bytes(b"preview")
|
||||
preview_store.refresh()
|
||||
|
||||
@routes.post("/__test__/thumbnail-folder/unlisted")
|
||||
async def unlisted_source(_request: web.Request) -> web.Response:
|
||||
await api._run_in_executor(unlisted)
|
||||
return web.json_response({"success": True})
|
||||
|
||||
return reset
|
||||
|
||||
@@ -147,3 +147,39 @@ test('another folder’s marker offers Use this folder', async ({ page, request
|
||||
await expect(notice).toHaveCount(0);
|
||||
await expect(block(page).locator('#location-status')).toHaveText(/^Now: one folder, .*thumbs\.$/);
|
||||
});
|
||||
|
||||
test('Overview shows the notice and Change location opens the row', async ({ page, request }) => {
|
||||
await expect(await request.post('/__test__/thumbnail-location', { data: { mode: 'folder' } })).toBeOK();
|
||||
await request.post('/__test__/thumbnail-folder/remove');
|
||||
await page.goto(`${workspace}#/system/overview`);
|
||||
const notice = page.locator('#unavailable-notice');
|
||||
await expect(notice.locator('h2')).toHaveText('The thumbnail folder is unavailable');
|
||||
await notice.getByRole('button', { name: 'Change location' }).click();
|
||||
await expect(page).toHaveURL(/#\/system\/sources$/);
|
||||
const sourcesNotice = page.locator('#unavailable-notice');
|
||||
await sourcesNotice.getByRole('button', { name: 'Change location' }).click();
|
||||
await expect(block(page).getByRole('radio', { name: /One folder/ })).toBeChecked();
|
||||
await expect(block(page).locator('#location-path')).toBeFocused();
|
||||
});
|
||||
|
||||
test('Thumbnail maintenance removes the thumbnails of removed sources in folder mode', async ({ page, request }) => {
|
||||
await expect(await request.post('/__test__/thumbnail-location', { data: { mode: 'folder' } })).toBeOK();
|
||||
await expect(await request.post('/__test__/thumbnail-folder/unlisted')).toBeOK();
|
||||
await openSources(page);
|
||||
const generation = page.locator('#workspace-main .operation').filter({ hasText: 'Thumbnail generation' });
|
||||
await generation.getByRole('button', { name: 'Review options' }).click();
|
||||
const scope = dialog(page).locator('#thumb-mode');
|
||||
await expect(scope.locator('option')).toHaveText(['Missing thumbnails only', 'Rebuild all (explicit opt-in)',
|
||||
'Convert outdated (removes older formats)', 'Remove thumbnails of removed sources']);
|
||||
await scope.selectOption('prune');
|
||||
await expect(dialog(page).locator('[role=status]')).toHaveText(
|
||||
/^1 source no longer listed: 1 thumbnail \(.+\) are removed\.$/);
|
||||
await expect(dialog(page).locator('dl.meta-grid dt')).toHaveText(['gone-0123abcd']);
|
||||
await expect(dialog(page)).toContainText('If a source comes back, its thumbnails are made again as you browse.');
|
||||
await scope.selectOption('missing');
|
||||
await expect(dialog(page).locator('dl.meta-grid')).toBeHidden();
|
||||
await scope.selectOption('prune');
|
||||
await dialog(page).getByRole('button', { name: 'Start' }).click();
|
||||
await expect(page.locator('#workspace-main h1')).toHaveText('Remove thumbnails of removed sources');
|
||||
await expect(page.locator('#workspace-main')).toContainText('1 thumbnails removed');
|
||||
});
|
||||
|
||||
@@ -371,6 +371,16 @@ test("ApiError keeps the job that applied a used plan (plan-used, System S7)", (
|
||||
}
|
||||
});
|
||||
|
||||
test("ApiError keeps a move refused for space with its review (#320)", () => {
|
||||
const plan = { totals: { files: 3 }, insufficient: { text: "Not enough space" } };
|
||||
assert.equal(new ApiError(507, "no space", "insufficient-space", null, { plan }).plan, plan);
|
||||
for (const bad of [null, "x", [1], undefined]) {
|
||||
assert.equal(new ApiError(507, "no space", "insufficient-space", null, { plan: bad }).plan, null);
|
||||
}
|
||||
assert.equal(new ApiError(409, "busy", "busy", null, { plan }).plan, null);
|
||||
assert.equal(new ApiError(507, "no space", "insufficient-space").plan, null);
|
||||
});
|
||||
|
||||
test("ApiError keeps a validated running job on busy and a validated source on invalid-source", () => {
|
||||
const body = { running: { id: "a".repeat(32), title: "Scan for metadata" }, source: "output/gone" };
|
||||
const busy = new ApiError(409, "busy", "busy", null, body);
|
||||
|
||||
@@ -115,6 +115,18 @@ test('a move and a prune show their own tiles in Activity', async () => {
|
||||
const prune = { kind: 'thumbnails', state: 'running', cancellable: true, scope: { mode: 'prune' },
|
||||
counters: { removed: 1, left: 0, errors: 0 } };
|
||||
assert.deepEqual(viewOf(prune).tiles.map(([, label]) => label), ['Removed', 'Left in place', 'Errors']);
|
||||
assert.equal(viewOf(move).line(move), '');
|
||||
assert.equal(viewOf(done).line(done), done.status_line);
|
||||
assert.equal(viewOf(move).kept(move), '3 moved');
|
||||
assert.equal(finishToast({ ...move, state: 'cancelled', status_line: '' }), viewOf(move).finishToast({ ...move,
|
||||
state: 'cancelled', status_line: '' }));
|
||||
const pruned = { ...prune, state: 'finished', status_line: '1 thumbnails removed' };
|
||||
assert.equal(viewOf(prune).line(prune), '');
|
||||
assert.equal(viewOf(pruned).line(pruned), '1 thumbnails removed');
|
||||
assert.equal(finishToast(pruned), '1 thumbnails removed');
|
||||
assert.equal(viewOf(prune).kept(prune), '1 removed');
|
||||
assert.match(finishToast({ ...prune, state: 'finished', status_line: '', title: 'Remove thumbnails of removed sources' }),
|
||||
/Remove thumbnails of removed sources/);
|
||||
const generate = { kind: 'thumbnails', state: 'running', cancellable: true, scope: { mode: 'missing' },
|
||||
counters: { generated: 0, skipped: 0, errors: 0 } };
|
||||
assert.deepEqual(viewOf(generate).tiles.map(([, label]) => label), ['Generated', 'Skipped', 'Errors']);
|
||||
|
||||
Reference in New Issue
Block a user