One icon per meaning across the UI; mark everything read in All Subscriptions
- All Subscriptions' header checks every feed and marks everything read (POST /api/read-all, the same feeds the view lists); it asks first. - Minus unsubscribes everywhere (the feed header's x read as "close"), x only closes or cancels, plus adds/subscribes/imports, and a dialog's confirm carries its action's icon. Remaining word buttons, the player and the folder arrow are Font Awesome 7.3.1 icons. - Toolbar grouped by what it acts on (add, unsubscribe, scan | play, read, keep); read and keep show the selected item's state. - The OPML subscription page uses the same header as a feed. - Fixed: Escape ignored inside a dialog's text box (Add feed could not be closed with it), white password box in the dark theme, stray dot in an undated item's details. - Tests: one action one icon across toolbar, page and all 8 dialogs; All Subscriptions mark everything read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0173mGu6rK18Ne7UGTwAaVJV
This commit is contained in:
@@ -455,7 +455,7 @@ test('Settings exports your OPML and imports a pasted one', async ({ page }) =>
|
||||
await page.locator('#prefs').click();
|
||||
const [dl] = await Promise.all([
|
||||
page.waitForEvent('download'),
|
||||
page.locator('#modalCard a', { hasText: 'Export OPML' }).click(),
|
||||
page.locator('#modalCard a[title="Export OPML"]').click(),
|
||||
]);
|
||||
expect(dl.suggestedFilename()).toBe('ipx-subscriptions.opml');
|
||||
const out = require('fs').readFileSync(await dl.path(), 'utf8');
|
||||
@@ -636,7 +636,7 @@ test('Popular lists what everyone here reads, but never a private feed', async (
|
||||
(await (await piper.request.get('/api/popular')).json()).find(p => p.id === 'test-show');
|
||||
const before = await row();
|
||||
expect(before.subscribed).toBe(false);
|
||||
await offered.filter({ hasText: 'Test Show' }).locator('button', { hasText: 'Subscribe' }).click();
|
||||
await offered.filter({ hasText: 'Test Show' }).locator('button[title="Subscribe"]').click();
|
||||
await expect(piper.locator('#feedlist .feed', { hasText: 'Test Show' })).toBeVisible({ timeout: 20_000 });
|
||||
|
||||
// Everyone counts, you included: it stays listed, marked as yours, with one more subscriber.
|
||||
@@ -679,3 +679,62 @@ test('a deleted file looks as if it was never downloaded', async ({ page }) => {
|
||||
await expect(page.locator('#files')).not.toContainText(/reaped/i);
|
||||
await expect(page.locator('#files button[title="Download to the server"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('one action, one icon: the toolbar, the page and every dialog agree', async ({ page }) => {
|
||||
const icon = loc => loc.locator('svg path').first().getAttribute('d');
|
||||
await page.locator('#feedlist .feed', { hasText: 'Test Show' }).first().click();
|
||||
|
||||
// Unsubscribe is a minus in the toolbar and the feed header, never the x that closes things.
|
||||
expect(await icon(page.locator('#content .acts [data-a="rm"]'))).toBe(await icon(page.locator('#tbRemove')));
|
||||
|
||||
// The toolbar's read and keep show the selected item's state, as its own buttons do, and follow
|
||||
// a change made from the toolbar.
|
||||
await page.locator('.ep').first().click();
|
||||
const pair = async a => [await icon(page.locator(a === 'read' ? '#tbRead' : '#tbFlag')),
|
||||
await icon(page.locator(`#detail [data-a="${a}"]`))];
|
||||
for (const a of ['read', 'flag']) { const [tb, own] = await pair(a); expect(tb).toBe(own); }
|
||||
const [kept] = await pair('flag');
|
||||
await page.locator('#tbFlag').click();
|
||||
await expect.poll(async () => { const [tb, own] = await pair('flag'); return tb === own && tb !== kept; }).toBe(true);
|
||||
await page.locator('#tbFlag').click(); // leave it as it was
|
||||
await expect.poll(async () => (await pair('flag'))[0]).toBe(kept);
|
||||
|
||||
// Every button in every dialog is an icon with its words in the tooltip.
|
||||
const dialogs = [
|
||||
() => page.locator('#addFeed').click(),
|
||||
() => page.locator('#prefs').click(),
|
||||
async () => { await page.locator('#prefs').click(); await page.locator('#gusers').click(); },
|
||||
async () => { await page.locator('#prefs').click(); await page.locator('#gopml').click(); },
|
||||
() => page.locator('#logs').click(),
|
||||
() => page.locator('#content .acts [data-a="settings"]').click(),
|
||||
() => page.locator('#content .acts [data-a="dl"]').click(),
|
||||
() => page.locator('#content .acts [data-a="rm"]').click(),
|
||||
];
|
||||
for (const open of dialogs) {
|
||||
await open();
|
||||
const btns = page.locator('#modalCard .btn');
|
||||
await expect(btns.first()).toBeVisible();
|
||||
for (const b of await btns.all()) {
|
||||
await expect(b.locator('svg')).toHaveCount(1);
|
||||
await expect(b).toHaveAttribute('title', /\S/);
|
||||
}
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(page.locator('#modal.on')).toBeHidden();
|
||||
}
|
||||
});
|
||||
|
||||
test('All Subscriptions marks everything read, across every feed', async ({ page }) => {
|
||||
const all = page.locator('#feedlist .place', { hasText: 'All Subscriptions' });
|
||||
await all.click();
|
||||
// Earlier tests read things; make sure something is unread. Opening an item reads it, and
|
||||
// its own button makes it unread again.
|
||||
await page.locator('.ep').first().click();
|
||||
await page.locator('#detail [data-a="read"][title="Mark unread"]').click();
|
||||
await expect(all.locator('.badge')).not.toHaveText('0');
|
||||
|
||||
page.once('dialog', d => d.accept());
|
||||
await page.locator('#content .acts [data-a="readall"]').click();
|
||||
await expect(all.locator('.badge')).toHaveText('0');
|
||||
await page.locator('.tabs button', { hasText: 'Unread' }).click();
|
||||
await expect(page.locator('.ep')).toHaveCount(0);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user