test(activity): cover the held and linked toast and a job view pruned by the server
Unit tests for the toast's 6.5 s and 20 s timeouts and its hover and focus hold, and a browser test for a finished job's open view after the server stops listing it. Coverage floors are only raised.
This commit is contained in:
@@ -643,3 +643,22 @@ test('an unknown or restarted job view says so', async ({ page, request }) => {
|
||||
await expect(fileRows(page)).toHaveCount(1);
|
||||
await expect(pill(page)).toBeHidden();
|
||||
});
|
||||
|
||||
// Review M3: a finished job the server no longer lists is gone from this tab too, and its open view says so.
|
||||
test('a finished job the server pruned leaves its open view honest', async ({ page, request }) => {
|
||||
await page.setViewportSize({ width: 1600, height: 1100 });
|
||||
const job = await startJob(request);
|
||||
await step(request, job.id, { processed: 1, records: [{ group: 'added', path: 'kept/row.png', detail: 'Added' }] });
|
||||
await finish(request, job.id);
|
||||
await openJob(page, job.id);
|
||||
await expect(fileRows(page)).toHaveCount(1);
|
||||
await filter(page).focus();
|
||||
await expect(await request.post('/__test__/jobs/restart')).toBeOK();
|
||||
// The next periodic list read (15 s) no longer has the finished job.
|
||||
test.slow();
|
||||
await expect(main(page).locator('section.surface p.empty-state')).toHaveText('This job is not in this session’s Activity. Jobs are kept until ComfyUI restarts.', { timeout: 20000 });
|
||||
await expect(progressSection(page)).toHaveCount(0);
|
||||
await expect(heading(page).locator('h1')).toHaveText('Activity');
|
||||
await expect(heading(page).locator('h1')).toBeFocused();
|
||||
await expect(page).toHaveTitle('Activity · Prompt Manager');
|
||||
});
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
// The shell's shared toast: plain messages clear after 6.5 s; a message with a link stays 20 s (D57), and any toast
|
||||
// stays while the pointer or keyboard focus rests on it.
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const source = import('../../web/js/workspace/toast.mjs');
|
||||
|
||||
class FakeToast extends EventTarget {
|
||||
constructor() { super(); this.held = false; this.nodes = []; this.text = ''; }
|
||||
get textContent() { return this.text; }
|
||||
set textContent(value) { this.text = value; this.nodes = []; }
|
||||
replaceChildren(...nodes) { this.nodes = nodes; this.text = nodes.map(node => node.textContent).join(''); }
|
||||
matches(selector) { assert.equal(selector, ':hover, :focus-within'); return this.held; }
|
||||
}
|
||||
|
||||
function install() {
|
||||
const toast = new FakeToast();
|
||||
const previous = global.document;
|
||||
global.document = {
|
||||
getElementById: id => id === 'workspace-toast' ? toast : null,
|
||||
createTextNode: text => ({ textContent: text }),
|
||||
createElement: tag => ({ tag, href: '', textContent: '' }),
|
||||
};
|
||||
return { toast, restore: () => { global.document = previous; } };
|
||||
}
|
||||
|
||||
test('a plain toast clears after 6.5 s and a linked one after 20 s', async t => {
|
||||
const { showWorkspaceToast, TOAST_MS, LINK_TOAST_MS } = await source;
|
||||
t.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
const { toast, restore } = install();
|
||||
try {
|
||||
showWorkspaceToast('Scan started.');
|
||||
assert.equal(toast.textContent, 'Scan started.');
|
||||
t.mock.timers.tick(TOAST_MS);
|
||||
assert.equal(toast.textContent, '');
|
||||
|
||||
showWorkspaceToast(['Another job is running: ', { text: 'Scan', href: '#/activity/x' }, '.']);
|
||||
assert.equal(toast.textContent, 'Another job is running: Scan.');
|
||||
assert.deepEqual(toast.nodes[1], { tag: 'a', href: '#/activity/x', textContent: 'Scan' });
|
||||
t.mock.timers.tick(TOAST_MS);
|
||||
assert.equal(toast.textContent, 'Another job is running: Scan.');
|
||||
t.mock.timers.tick(LINK_TOAST_MS - TOAST_MS);
|
||||
assert.equal(toast.textContent, '');
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('a toast under the pointer or focus waits until they leave', async t => {
|
||||
const { showWorkspaceToast, TOAST_MS } = await source;
|
||||
t.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
const { toast, restore } = install();
|
||||
try {
|
||||
showWorkspaceToast('Prompt copied.');
|
||||
toast.held = true;
|
||||
t.mock.timers.tick(TOAST_MS);
|
||||
assert.equal(toast.textContent, 'Prompt copied.', 'held: it stays');
|
||||
// Focus moving inside the toast keeps it; leaving it starts a fresh 6.5 s.
|
||||
toast.dispatchEvent(new Event('focusout'));
|
||||
t.mock.timers.tick(0);
|
||||
toast.dispatchEvent(new Event('pointerleave'));
|
||||
assert.equal(toast.textContent, 'Prompt copied.');
|
||||
toast.held = false;
|
||||
toast.dispatchEvent(new Event('focusout'));
|
||||
t.mock.timers.tick(0);
|
||||
t.mock.timers.tick(TOAST_MS - 1);
|
||||
assert.equal(toast.textContent, 'Prompt copied.');
|
||||
t.mock.timers.tick(1);
|
||||
assert.equal(toast.textContent, '');
|
||||
|
||||
// A newer toast replaces a held one and stops waiting for the old one's pointer.
|
||||
showWorkspaceToast('First.');
|
||||
toast.held = true;
|
||||
t.mock.timers.tick(TOAST_MS);
|
||||
showWorkspaceToast('Second.');
|
||||
toast.held = false;
|
||||
toast.dispatchEvent(new Event('pointerleave'));
|
||||
t.mock.timers.tick(TOAST_MS - 1);
|
||||
assert.equal(toast.textContent, 'Second.');
|
||||
t.mock.timers.tick(1);
|
||||
assert.equal(toast.textContent, '');
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('a page without the toast shows nothing and does not fail', async () => {
|
||||
const { showWorkspaceToast } = await source;
|
||||
const previous = global.document;
|
||||
global.document = { getElementById: () => null };
|
||||
try {
|
||||
assert.doesNotThrow(() => showWorkspaceToast('Nothing.'));
|
||||
} finally {
|
||||
global.document = previous;
|
||||
}
|
||||
});
|
||||
+4
-4
@@ -157,7 +157,7 @@
|
||||
},
|
||||
"workspace/activity-job-view.mjs": {
|
||||
"lines": 97,
|
||||
"branches": 82,
|
||||
"branches": 83,
|
||||
"functions": 98,
|
||||
"statements": 97
|
||||
},
|
||||
@@ -169,8 +169,8 @@
|
||||
},
|
||||
"workspace/activity-store.mjs": {
|
||||
"lines": 98,
|
||||
"branches": 94,
|
||||
"functions": 95,
|
||||
"branches": 95,
|
||||
"functions": 98,
|
||||
"statements": 98
|
||||
},
|
||||
"workspace/activity.mjs": {
|
||||
@@ -349,7 +349,7 @@
|
||||
},
|
||||
"workspace/toast.mjs": {
|
||||
"lines": 98,
|
||||
"branches": 86,
|
||||
"branches": 98,
|
||||
"functions": 98,
|
||||
"statements": 98
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user