fix(images): rewrite the live status only when its text changes
A star press republishes the stream, and the textContent setter replaced the continuation status, the count and the caption date with the same text, so a polite role="status" region could announce it again. Text and the role attribute are now written only when they change. Failing before: a MutationObserver saw 10 records on those nodes after a favorite toggle; now none.
This commit is contained in:
@@ -1435,3 +1435,26 @@ test('Import Rescan shows that it runs, ignores a second press and announces the
|
||||
await expect(cards(page)).toHaveCount(3);
|
||||
await expect(caption(page)).toContainText('3 of 3 images');
|
||||
});
|
||||
|
||||
// #249 final review I2: a polite live region re-announces replaced text, so an unchanged status is never rewritten.
|
||||
test('a star press leaves the continuation status, count and caption date untouched', async ({ page }) => {
|
||||
await page.goto(workspace);
|
||||
await expect(cards(page)).toHaveCount(2);
|
||||
await expect(page.locator('#gallery-continuation p')).toHaveText(/caught up/);
|
||||
await expect(caption(page).locator('span').nth(1)).toBeVisible();
|
||||
await page.evaluate(() => {
|
||||
window.__statusMutations = [];
|
||||
const observer = new MutationObserver(records => {
|
||||
window.__statusMutations.push(...records.map(record => `${record.type}:${record.target.nodeName}`));
|
||||
});
|
||||
for (const node of [document.querySelector('#gallery-continuation p'), document.querySelector('#result-count'),
|
||||
document.querySelector('.section-caption > span:nth-child(2)')]) {
|
||||
observer.observe(node, { childList: true, characterData: true, subtree: true, attributes: true });
|
||||
}
|
||||
});
|
||||
const card = page.locator('article[data-image-key="0:shared/same.png"]');
|
||||
await star(card, 'same.png').click();
|
||||
await expectStar(star(card, 'same.png'), true);
|
||||
await expect(toast(page)).toHaveText('Added to favorites.');
|
||||
expect(await page.evaluate(() => window.__statusMutations)).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -20,6 +20,17 @@ function el(tag, text, className) {
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Live regions re-announce replaced text even when it is the same, and a patched publish (a favorite) repeats the
|
||||
* same status: write text and attributes only when they change.
|
||||
*/
|
||||
function setText(element, text) {
|
||||
if (element.textContent !== text) element.textContent = text;
|
||||
}
|
||||
function setAttr(element, name, value) {
|
||||
if (element.getAttribute(name) !== value) element.setAttribute(name, value);
|
||||
}
|
||||
|
||||
function selectOf(id, options, value) {
|
||||
const select = el('select');
|
||||
select.id = id;
|
||||
@@ -458,10 +469,10 @@ export function mountImages(main, api, parentScope, {embedded = false, fetchPage
|
||||
for (let index = state?.items.length || 0; index < next.items.length; index++) itemIndexes.set(imageKey(next.items[index]), index);
|
||||
}
|
||||
state = next;
|
||||
count.textContent = embedded ? `${next.loaded.toLocaleString('en-US')} loaded · ${next.total.toLocaleString('en-US')} ${noun(next.total, 'linked image')}` : countLine(next);
|
||||
setText(count, embedded ? `${next.loaded.toLocaleString('en-US')} loaded · ${next.total.toLocaleString('en-US')} ${noun(next.total, 'linked image')}` : countLine(next));
|
||||
if (!embedded) {
|
||||
captionDay.textContent = captionDate(next.items, filters.sort);
|
||||
captionDay.hidden = !captionDay.textContent;
|
||||
setText(captionDay, captionDate(next.items, filters.sort));
|
||||
if (captionDay.hidden !== !captionDay.textContent) captionDay.hidden = !captionDay.textContent;
|
||||
}
|
||||
if (changed) updateLayout();
|
||||
// The concept's Load more toast (app.js loadMoreImages) for every batch appended to a loaded collection.
|
||||
@@ -469,8 +480,8 @@ export function mountImages(main, api, parentScope, {embedded = false, fetchPage
|
||||
const added = next.loaded - previous.loaded;
|
||||
showWorkspaceToast(`${added.toLocaleString('en-US')} more ${noun(added, 'image')} loaded. ${next.loaded.toLocaleString('en-US')} of ${next.total.toLocaleString('en-US')}.`);
|
||||
}
|
||||
message.textContent = continuationText(next);
|
||||
if (next.error) message.setAttribute('role', 'alert'); else message.setAttribute('role', 'status');
|
||||
setText(message, continuationText(next));
|
||||
setAttr(message, 'role', next.error ? 'alert' : 'status');
|
||||
retry.hidden = !next.error;
|
||||
more.hidden = !next.hasMore || !!next.error;
|
||||
more.setAttribute('aria-disabled', String(next.loading || recovering));
|
||||
|
||||
Reference in New Issue
Block a user