A new picture puts itself first in its line, so a press on a picture ended on another one and was no click. While the main button is held on a button of the list, what comes waits until the press and its click are over. Tested with a real mouse on an isolated ComfyUI: a press held until a new picture came opened the pressed picture 4 times of 4 (0 before), and 16 of 16 long presses during runs answered (13 before). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
211 lines
11 KiB
HTML
211 lines
11 KiB
HTML
<!doctype html>
|
|
<meta charset="utf-8">
|
|
<title>Saves bar and page regression</title>
|
|
<link rel="stylesheet" href="../web/styles/28-workflows.css">
|
|
<script type="importmap">{"imports":{"/scripts/app.js":"data:text/javascript,export const app = {};","/scripts/api.js":"data:text/javascript,export const api = new EventTarget();"}}</script>
|
|
<pre id="results">Running…</pre>
|
|
<div class="graph-canvas-panel"></div>
|
|
<div id="page"></div>
|
|
<script type="module">
|
|
import { app } from '/scripts/app.js';
|
|
import { api } from '/scripts/api.js';
|
|
import { translate as t } from '../web/modules/locales.js';
|
|
import { MAX_RUNS } from '../web/modules/snapshot_plan.js';
|
|
import { refreshGroup } from '../web/modules/snapshot_store.js';
|
|
import { savesToggle } from '../web/modules/ui_saves_list.js';
|
|
import { watchSaves } from '../web/modules/ui_snapshot_bar.js';
|
|
|
|
// The real bar and Saves page. The browser sends no click to an element that left the page during the press,
|
|
// so what must hold is that nothing here takes a control off it. Only ComfyUI and the saves routes are fake.
|
|
const save = id => ({ id, at: 0, count: 1, changes: [], pictures: [], running: 0 });
|
|
const group = { key: 'k', title: '', path: '', saves: [save('a'), save('b')] };
|
|
let slow = 0; // how long a count's answer takes
|
|
let refused = 0; // the count that is not taken
|
|
window.fetch = async (url, options) => {
|
|
if (String(url).includes('/saves/update')) {
|
|
await new Promise(resolve => setTimeout(resolve, slow));
|
|
const { id, count } = JSON.parse(options.body);
|
|
if (count === refused) return new Response(JSON.stringify({ message: 'disk full' }), { status: 500 });
|
|
group.saves.find(item => item.id === id).count = count;
|
|
}
|
|
return new Response(JSON.stringify({ group, groups: [{ key: 'k', title: 'canvas', path: '', count: group.saves.length, updated: 0, cover: null }], workflow: {} }));
|
|
};
|
|
app.rootGraph = { extra: { anomalous_saves: 'k' }, serialize: () => ({}) };
|
|
app.graph = app.rootGraph;
|
|
app.extensionManager = { workflow: { activeWorkflow: { path: 'a.json', changeTracker: { undoQueue: [], redoQueue: [], updateState: async () => {} } } } };
|
|
|
|
const results = document.querySelector('#results');
|
|
let failures = 0;
|
|
const check = (condition, text) => { results.textContent += `${condition ? 'PASS' : 'FAIL'} ${text}\n`; if (!condition) failures++; };
|
|
const one = selector => document.querySelector(selector);
|
|
const all = selector => [...document.querySelectorAll(selector)];
|
|
const answered = () => new Promise(resolve => setTimeout(resolve, 30));
|
|
const typed = async (box, value) => {
|
|
box.value = value;
|
|
box.dispatchEvent(new Event('change'));
|
|
await answered();
|
|
};
|
|
try {
|
|
results.textContent = '';
|
|
watchSaves(() => null);
|
|
await answered();
|
|
one('.anomalous-snapshot-number').click();
|
|
await answered();
|
|
check(all('.anomalous-snapshot-mark.is-current').length === 1, 'a number lights when the canvas goes back to it');
|
|
|
|
const controls = all('#anomalous-snapshot-bar button, #anomalous-snapshot-bar input');
|
|
const stayed = () => controls.every(node => node.isConnected);
|
|
api.dispatchEvent(new CustomEvent('graphChanged'));
|
|
check(!one('.is-current'), 'a change darkens the lit number');
|
|
check(stayed(), 'a change keeps every control on the page: ComfyUI tells of it between a press and its click');
|
|
|
|
const [box, other] = all('.anomalous-snapshot-count');
|
|
await typed(box, '3');
|
|
check(group.saves[0].count === 3, 'a count is sent');
|
|
check(stayed(), 'the answer to a count keeps every control on the page: it comes during the press on the next one');
|
|
check(one('.anomalous-snapshot-run').textContent.includes('4'), 'Run says the new total');
|
|
await typed(box, '5');
|
|
check(group.saves[0].count === 5, 'a box that was redrawn sends what it holds');
|
|
|
|
box.focus();
|
|
box.value = '7';
|
|
group.saves[1].count = 2;
|
|
await refreshGroup('k');
|
|
check(document.activeElement === box && box.value === '7', 'a run ending leaves a box being typed in alone');
|
|
check(other.value === '2', 'the other boxes come up to date');
|
|
check(stayed(), 'a run ending keeps every control on the page');
|
|
box.blur();
|
|
|
|
refused = 9;
|
|
await typed(box, '9');
|
|
check(box.value === '5' && group.saves[0].count === 5, 'a count not taken goes back to the one stored');
|
|
check(all('.anomalous-mixer-toast').at(-1)?.textContent === t('snapshotCountFailed', { why: 'disk full' }), 'and says so');
|
|
box.focus();
|
|
await typed(box, '9');
|
|
check(document.activeElement === box && box.value === '5', 'in a box that keeps the focus too, after Enter');
|
|
box.blur();
|
|
slow = 10;
|
|
box.value = '6';
|
|
box.dispatchEvent(new Event('change'));
|
|
box.value = '9';
|
|
box.dispatchEvent(new Event('change'));
|
|
await answered();
|
|
check(box.value === '6', 'behind a count that was taken, it goes back to that one');
|
|
slow = 0;
|
|
refused = 0;
|
|
await typed(box, '5');
|
|
check(group.saves[0].count === 5, 'and the box goes on working');
|
|
|
|
group.saves.push(save('c'));
|
|
await refreshGroup('k');
|
|
check(all('.anomalous-snapshot-number').length === 3, 'a new save shows');
|
|
group.saves.shift();
|
|
await refreshGroup('k');
|
|
const left = all('.anomalous-snapshot-mark');
|
|
check(left.map(chip => chip.dataset.key).join() === 'b,c' && all('.anomalous-snapshot-count')[0].value === '2', 'a part left by a removed save does not go on as the next one');
|
|
|
|
one('.anomalous-snapshot-close').click();
|
|
const handle = one('.anomalous-snapshot-unfold');
|
|
await refreshGroup('k');
|
|
check(handle.isConnected, 'the folded handle stays on the page too');
|
|
handle.click();
|
|
check(one('.anomalous-snapshot-add') && one('.anomalous-snapshot-status') && !one('.anomalous-snapshot-unfold'), 'unfolding brings the row back');
|
|
|
|
const picture = filename => ({ filename, subfolder: '', type: 'output' });
|
|
group.saves[0].pictures = [picture('one.png')];
|
|
const owner = { recipeView: one('#page'), close() {}, show() {}, refreshRecipes() {} };
|
|
savesToggle(owner);
|
|
owner.savesOpen.add('k');
|
|
owner.savesToggle.click();
|
|
await answered();
|
|
const pageControls = all('.anomalous-saves-part button, .anomalous-saves-part input');
|
|
const pageStayed = () => pageControls.every(node => node.isConnected);
|
|
check(all('.anomalous-saves-save').length === 2 && one('.anomalous-saves-picture'), 'the page shows the saves and their pictures');
|
|
|
|
const pageBox = one('.anomalous-saves-tools .anomalous-snapshot-count');
|
|
await typed(pageBox, '4');
|
|
check(group.saves[0].count === 4 && pageStayed(), 'on the page too, the answer to a count keeps every control there');
|
|
check(one('.anomalous-saves-head .anomalous-snapshot-run').textContent.includes('5'), 'the part says the new total');
|
|
pageBox.focus();
|
|
pageBox.value = '8';
|
|
await refreshGroup('k');
|
|
check(document.activeElement === pageBox && pageBox.value === '8' && pageStayed(), 'a run ending leaves the part alone, a box being typed in too');
|
|
pageBox.blur();
|
|
|
|
const more = one('.anomalous-saves-tools .anomalous-wf-icon-btn');
|
|
more.click();
|
|
const [button, menu] = [one('.anomalous-saves-tools .anomalous-wf-icon-btn').getBoundingClientRect(), one('.anomalous-popup-menu').getBoundingClientRect()];
|
|
check(Math.abs(menu.right - button.right) < 2, 'a redrawn card\'s menu opens by its button');
|
|
more.click();
|
|
check(!one('.anomalous-popup-menu'), 'a second press closes it');
|
|
|
|
const tiles = all('.anomalous-saves-picture');
|
|
await refreshGroup('k');
|
|
check(tiles.every(node => node.isConnected), 'pictures that did not change stay on the page');
|
|
group.saves[0].pictures.push(picture('two.png'));
|
|
await refreshGroup('k');
|
|
const thumbs = all('.anomalous-saves-picture img');
|
|
check(thumbs.length === 2 && thumbs[0].src.includes('two.png'), 'a new picture goes first in its line');
|
|
|
|
const press = (node, button = 0) => node.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, button }));
|
|
const firstThumb = () => one('.anomalous-saves-picture img').src;
|
|
const pressed = one('.anomalous-saves-picture');
|
|
press(pressed.querySelector('img'));
|
|
group.saves[0].pictures.push(picture('three.png'));
|
|
await refreshGroup('k');
|
|
check(pressed.isConnected && firstThumb().includes('two.png'), 'a press on a picture holds its line: a new one would put another under the pointer');
|
|
group.saves[0].pictures.push(picture('four.png'));
|
|
await refreshGroup('k');
|
|
window.dispatchEvent(new PointerEvent('pointerup'));
|
|
check(pressed.isConnected, 'its click still finds it as the press ends');
|
|
await answered();
|
|
check(firstThumb().includes('four.png'), 'what came meanwhile shows after the press, as it stands last');
|
|
|
|
press(one('.anomalous-saves-picture'));
|
|
group.saves[0].pictures.push(picture('five.png'));
|
|
await refreshGroup('k');
|
|
window.dispatchEvent(new PointerEvent('pointercancel'));
|
|
await answered();
|
|
check(firstThumb().includes('five.png'), 'a press the browser takes over, a drag, ends the hold too');
|
|
|
|
press(one('.anomalous-saves-picture'), 2);
|
|
group.saves[0].pictures.push(picture('six.png'));
|
|
await refreshGroup('k');
|
|
check(firstThumb().includes('six.png'), 'a right press holds nothing: no click follows it, its pointerup may never come');
|
|
press(one('.anomalous-saves-list'));
|
|
group.saves[0].pictures.push(picture('seven.png'));
|
|
await refreshGroup('k');
|
|
check(firstThumb().includes('seven.png'), 'nor does a press beside the buttons, on the scrollbar say');
|
|
group.saves[0].pictures.length = 2;
|
|
await refreshGroup('k');
|
|
|
|
group.saves[0].count = MAX_RUNS + 1;
|
|
await refreshGroup('k');
|
|
one('.anomalous-saves-head .anomalous-snapshot-run').click();
|
|
await answered();
|
|
check(one('.anomalous-saves-status').textContent, 'a refused run says so on the part it has after a redraw');
|
|
group.saves[0].count = 1;
|
|
|
|
one('.anomalous-saves-fold').click();
|
|
check(!one('.anomalous-saves-save'), 'a part folds');
|
|
one('.anomalous-saves-fold').click();
|
|
await answered();
|
|
check(all('.anomalous-saves-save').length === 2 && all('.anomalous-saves-picture').length === 2, 'and opens with its cards and pictures');
|
|
|
|
group.saves.shift();
|
|
await refreshGroup('k');
|
|
check(all('.anomalous-saves-save').map(card => card.dataset.key).join() === 'c' && one('.anomalous-saves-number').textContent === '1', 'a card left by a removed save does not go on as the next one');
|
|
|
|
group.saves.push({ ...save('d'), count: MAX_RUNS - 1 });
|
|
await refreshGroup('k');
|
|
slow = 60;
|
|
const late = one('.anomalous-snapshot-mark .anomalous-snapshot-count');
|
|
late.value = '2'; // one over, once it counts
|
|
late.dispatchEvent(new Event('change'));
|
|
one('#anomalous-snapshot-bar .anomalous-snapshot-run').click();
|
|
await new Promise(resolve => setTimeout(resolve, 150));
|
|
check(one('.anomalous-snapshot-status').textContent === t('snapshotTooMany', { most: MAX_RUNS }), 'Run goes by a count whose answer comes after the click');
|
|
results.textContent += failures ? `${failures} FAILED` : 'ALL PASSED';
|
|
} catch (error) { results.textContent += `FAIL ${error.stack}`; }
|
|
</script>
|