Refresh buttons turn from the press to the end of their scan; quick checks keep the count (#232, #233)

The buttons followed the feeds being checked, so they stopped between one feed and the next;
7ecbe82 held them for 600 ms over each gap. Now pressing one starts it, and scan_done stops
it only when it is the end of the scan asked for: scan_done carries `only` and `forced`, so a
scheduled scan finishing first does not stop a button whose scan is queued behind it. An
error event, a job that failed without a scan_done, stops them all.

A row's spinner stood in for its unread count from the moment its feed was checked; most
checks take milliseconds, so each count blinked as the scan went by. It now waits 300 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 15:00:50 +00:00
parent 7ecbe8293f
commit a5e64e520d
9 changed files with 87 additions and 36 deletions

View File

@@ -1697,15 +1697,30 @@ test('a pinned feed, even one from inside a folder, goes to the top of the list'
test('a feed being checked shows a spinner on its row, and no toast', async ({ page }) => {
const row = page.locator('#feedlist .feed', { hasText: 'Test Show' });
await expect(row).toBeVisible();
await page.evaluate(() => setScanning('test-show', true));
// A check of a few milliseconds leaves the count where it is, or every count blinked as a scan
// went by; the spinner comes once the check has taken a moment (#232).
const atOnce = await page.evaluate(() => {
setScanning('test-show', true);
return document.querySelector('#feedlist .feed[data-id="test-show"]').classList.contains('scanning');
});
expect(atOnce, 'no spinner the moment a check starts').toBe(false);
await expect(row).toHaveClass(/\bscanning\b/);
await expect(row.locator('.badge'), 'the spinner stands in for the count').toBeHidden();
await page.evaluate(() => setScanning('test-show', false));
await expect(row).not.toHaveClass(/\bscanning\b/);
await expect(row.locator('.badge')).toBeVisible();
// The button turns from the press to the end of the scan it asked for (#233): a scheduled
// scan's end, or the end of a scan of one other feed, leaves it turning.
const spins = () => document.body.classList.contains('scan-any');
expect(await page.evaluate(spins => { spinStart(null); spinEnd({ only: null, forced: false });
const a = document.body.classList.contains('scan-any'); spinEnd({ only: 'test-show', forced: true });
return a && document.body.classList.contains('scan-any'); })).toBe(true);
await page.evaluate(() => spinEnd({ only: null, forced: true }));
expect(await page.evaluate(spins)).toBe(false);
// Checking every feed says so on the rows, not in a toast (issue #37).
await page.locator('#scanAll').click();
await page.waitForTimeout(1500);
expect(await page.evaluate(() => { scanAll(); return document.body.classList.contains('scan-any'); }),
'turning the moment it is pressed').toBe(true);
await expect(page.locator('body')).not.toHaveClass(/\bscan-any\b/, { timeout: 15_000 });
await expect(page.locator('.toast', { hasText: /Scanning|Checking| new/ })).toHaveCount(0);
});
@@ -1766,20 +1781,28 @@ test('Share hands the feed, the item and the file to the share sheet', async ({
]);
});
test('a refresh button turns while what it checks is being checked', async ({ page }) => {
test('a refresh button turns from the press to the end of the scan it asked for', async ({ page }) => {
await page.locator('.feed', { hasText: 'Test Show' }).click();
const spin = sel => page.locator(sel).evaluate(el => getComputedStyle(el).animationName);
const btn = '.fhead [data-a=scan] .i';
await expect(page.locator(btn)).toBeVisible();
expect(await spin(btn)).toBe('none');
await page.evaluate(() => setScanning('test-show', true));
// Pressed: this feed's button turns, the toolbar's does not (#78, #233).
await page.evaluate(() => spinStart('test-show'));
expect(await spin(btn)).toBe('ipxspin');
expect(await spin('#scanAll .i')).toBe('ipxspin');
// Another feed being checked turns the toolbar's, not this page's.
await page.evaluate(() => { setScanning('test-show', false); setScanning('multi-show', true); });
expect(await spin('#scanAll .i')).toBe('none');
// Feeds being checked one by one no longer start or stop it (#231).
await page.evaluate(() => { setScanning('test-show', true); setScanning('test-show', false); });
expect(await spin(btn)).toBe('ipxspin');
// Another feed's scan ending, or a scheduled one's, leaves it turning; its own stops it.
await page.evaluate(() => { spinEnd({ only: 'multi-show', forced: true }); spinEnd({ only: null, forced: false }); });
expect(await spin(btn)).toBe('ipxspin');
await page.evaluate(() => spinEnd({ only: 'test-show', forced: true }));
expect(await spin(btn)).toBe('none');
// The toolbar's turns for a scan of every feed, until that scan ends.
await page.evaluate(() => spinStart(null));
expect(await spin('#scanAll .i')).toBe('ipxspin');
await page.evaluate(() => setScanning('multi-show', false));
await page.evaluate(() => spinEnd({ only: null, forced: true }));
expect(await spin('#scanAll .i')).toBe('none');
});