fix(activity): indeterminate pill bar and the nothing-to-do line
The pill's mini bar sweeps while a job's total is unknown instead of showing an empty fill. A finished thumbnail job with nothing to do shows the server's final line. Coverage floors raised for gallery.js and activity.mjs.
This commit is contained in:
@@ -54,8 +54,8 @@ async function step(request, id, data) {
|
||||
await expect(await request.post(`/__test__/jobs/${id}/step`, { data })).toBeOK();
|
||||
}
|
||||
|
||||
async function finish(request, id, state = 'finished') {
|
||||
const response = await request.post(`/__test__/jobs/${id}/finish`, { data: { state } });
|
||||
async function finish(request, id, state = 'finished', extra = {}) {
|
||||
const response = await request.post(`/__test__/jobs/${id}/finish`, { data: { state, ...extra } });
|
||||
await expect(response).toBeOK();
|
||||
return (await response.json()).job;
|
||||
}
|
||||
@@ -1050,8 +1050,13 @@ test('a held legacy job and the health check show no tiles, no Results and no ma
|
||||
await expect(response).toBeOK();
|
||||
const held = (await response.json()).job;
|
||||
await page.goto(`${workspace}#/images`);
|
||||
// D112: the pill names the held operation and shows no percentage.
|
||||
// D112: the pill names the held operation and shows no percentage; its bar is indeterminate, not an empty fill
|
||||
// (S5 review M5), and stays hidden from assistive tech, which reads the label.
|
||||
await expect(pill(page)).toHaveText('Thumbnail clear (gallery page)');
|
||||
await expect(pill(page)).toHaveAttribute('aria-label', 'Thumbnail clear (gallery page)');
|
||||
await expect(pill(page).locator('.activity-pill-bar')).toHaveClass('activity-pill-bar indeterminate');
|
||||
await expect(pill(page).locator('.activity-pill-bar')).toHaveAttribute('aria-hidden', 'true');
|
||||
expect(await pill(page).locator('.activity-pill-bar i').evaluate(node => node.style.width)).toBe('');
|
||||
await page.goto(`${workspace}#/activity`);
|
||||
const item = items(page).filter({ has: page.getByRole('link', { name: /Thumbnail clear \(gallery page\)$/ }) });
|
||||
await expect(item.locator('p')).toHaveText(/^Started \d\d:\d\d · closing this view does not cancel it$/);
|
||||
@@ -1082,3 +1087,17 @@ test('a held legacy job and the health check show no tiles, no Results and no ma
|
||||
await finish(request, health.id);
|
||||
await expect(toast(page)).toHaveText('Health check finished.');
|
||||
});
|
||||
|
||||
// S5 review I3: a thumbnail job with nothing to generate ends on the server's final line ("No previews to generate · N
|
||||
// already up to date"), never on a stale "Processing file 0 of 0" under the Completed chip.
|
||||
test('a finished thumbnail job with nothing to do says so', async ({ page, request }) => {
|
||||
await page.setViewportSize({ width: 1600, height: 1100 });
|
||||
const job = await startJob(request, { ...THUMB_JOB, total: 0 });
|
||||
await step(request, job.id, { total: 0, processed: 0 }); // the stale line an older server left
|
||||
await openJob(page, job.id);
|
||||
const line = progressSection(page).locator('.panel-heading > span.small');
|
||||
await finish(request, job.id, 'finished', { status_line: 'No previews to generate · 24 already up to date' });
|
||||
await expect(line).toHaveText('No previews to generate · 24 already up to date');
|
||||
await expect(page.getByText('Processing file 0 of 0')).toHaveCount(0);
|
||||
await expect(progressSection(page).locator('progress')).toHaveAttribute('value', '100');
|
||||
});
|
||||
|
||||
@@ -16,7 +16,8 @@ reading files, so a test sets its progress and counters exactly:
|
||||
rows), sets ``processed`` (and ``total``) and ``counters``, then publishes
|
||||
the progress to every subscriber;
|
||||
- ``POST /__test__/jobs/{id}/finish`` ends it as ``finished``, ``cancelled``
|
||||
or ``failed``; a cancel through the real route ends it ``cancelled``;
|
||||
or ``failed``, with the runner's final ``status_line`` when given; a cancel
|
||||
through the real route ends it ``cancelled``;
|
||||
- ``POST /__test__/jobs/hold`` opens a legacy-page hold (``kind``, ``title``,
|
||||
``admission``, default ``records``): a running, non-cancellable job with no
|
||||
outcome groups, as a legacy restore, maintenance or thumbnail clear request
|
||||
@@ -259,9 +260,12 @@ def register_activity_commands(app, routes, api):
|
||||
@routes.post("/__test__/jobs/{job_id}/finish")
|
||||
async def finish_job(request):
|
||||
job = job_or_404(request)
|
||||
state = (await body(request)).get("state", "finished")
|
||||
data = await body(request)
|
||||
state = data.get("state", "finished")
|
||||
if state not in FINAL_STATES:
|
||||
raise web.HTTPBadRequest()
|
||||
if isinstance(data.get("status_line"), str): # the runner's final line
|
||||
job.update(status_line=data["status_line"])
|
||||
ending = endings[job.id]
|
||||
if not ending.done():
|
||||
ending.set_result(state)
|
||||
|
||||
+5
-5
@@ -42,10 +42,10 @@
|
||||
"statements": 98
|
||||
},
|
||||
"gallery.js": {
|
||||
"lines": 41,
|
||||
"branches": 63,
|
||||
"functions": 45,
|
||||
"statements": 41
|
||||
"lines": 45,
|
||||
"branches": 64,
|
||||
"functions": 48,
|
||||
"statements": 45
|
||||
},
|
||||
"html-escape.js": {
|
||||
"lines": 98,
|
||||
@@ -181,7 +181,7 @@
|
||||
},
|
||||
"workspace/activity.mjs": {
|
||||
"lines": 96,
|
||||
"branches": 90,
|
||||
"branches": 91,
|
||||
"functions": 98,
|
||||
"statements": 96
|
||||
},
|
||||
|
||||
@@ -22,7 +22,7 @@ import { navigateTo } from './state.mjs';
|
||||
import { pageHeading } from './heading.mjs';
|
||||
import { showWorkspaceToast } from './toast.mjs';
|
||||
import { pillParts, outcomeLine, LOST_DETAIL } from './activity-store.mjs';
|
||||
import { viewOf, canCancel, showProgress } from './activity-kinds.mjs';
|
||||
import { viewOf, canCancel, showProgress, indeterminate } from './activity-kinds.mjs';
|
||||
|
||||
export const ACTIVITY_DESCRIPTION = 'Scans, tagging, downloads and maintenance share one history.';
|
||||
export const EMPTY_ACTIVITY = 'No jobs have run since ComfyUI started. Scans and other long operations appear here.';
|
||||
@@ -110,7 +110,10 @@ export function mountActivityTopbar({ store, scope, doc = document }) {
|
||||
const parts = pillParts(job);
|
||||
label.textContent = parts.label;
|
||||
files.textContent = parts.files;
|
||||
fill.style.width = `${job.progress}%`;
|
||||
// D111 (S5 review M5): no total, no fill: the bar sweeps instead of showing an empty determinate 0%.
|
||||
const unknown = indeterminate(job);
|
||||
bar.classList.toggle('indeterminate', unknown);
|
||||
fill.style.width = unknown ? '' : `${job.progress}%`;
|
||||
pill.href = `#/activity/${job.id}`;
|
||||
pill.setAttribute('aria-label', parts.text);
|
||||
};
|
||||
|
||||
@@ -24,6 +24,10 @@ main:focus-visible { outline: 2px solid var(--ca-focus); outline-offset: 4px; }
|
||||
.activity-pill:hover { border-color: var(--ca-accent); /* D53 */ }
|
||||
.activity-pill-bar { display: block; inline-size: 70px; block-size: 5px; border-radius: 3px; background: var(--line); overflow: hidden; /* D53 */ }
|
||||
.activity-pill-bar i { display: block; block-size: 100%; inline-size: 0; background: var(--ca-accent); /* D53 */ }
|
||||
/* D111 (S5): a running job without a known total sweeps a short segment; reduced motion shows a dimmed full bar. */
|
||||
.activity-pill-bar.indeterminate i { inline-size: 40%; animation: activity-pill-sweep 1.4s ease-in-out infinite; /* D111 */ }
|
||||
@keyframes activity-pill-sweep { from { transform: translateX(-100%); } to { transform: translateX(250%); } }
|
||||
@media (prefers-reduced-motion: reduce) { .activity-pill-bar.indeterminate i { inline-size: 100%; opacity: .45; animation: none; /* D111 */ } }
|
||||
@media (pointer: coarse) { .activity-pill:not([hidden]) { min-block-size: 40px; /* D53 */ } }
|
||||
@media (max-width: 700px) {
|
||||
/* D52: with the Activity button the full-width row can be wider than a zoomed phone, so its items wrap. */
|
||||
|
||||
Reference in New Issue
Block a user