mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 14:16:28 +03:00
Fix live toast stale callback race (#271)
Co-authored-by: Jean-Claude <273834277+jjoanna2-debug@users.noreply.github.com>
This commit is contained in:
co-authored by
Jean-Claude
parent
9798bb7235
commit
7501e67b55
@@ -7928,7 +7928,7 @@ void main() {
|
||||
const barTopFromBottom = barRect && barRect.height > 0
|
||||
? Math.max(16, window.innerHeight - barRect.top + 12)
|
||||
: 16;
|
||||
toastEl = el('div', {
|
||||
const currentToast = el('div', {
|
||||
position: 'fixed', bottom: barTopFromBottom + 'px', left: '50%',
|
||||
transform: 'translateX(-50%) translateY(8px)',
|
||||
background: C.ink, color: C.white,
|
||||
@@ -7938,19 +7938,24 @@ void main() {
|
||||
transition: 'opacity 0.25s ' + EASE + ', transform 0.25s ' + EASE,
|
||||
pointerEvents: 'none', maxWidth: '420px', textAlign: 'center',
|
||||
});
|
||||
toastEl.id = PREFIX + '-toast';
|
||||
toastEl.textContent = message;
|
||||
uiAppend(toastEl);
|
||||
toastEl = currentToast;
|
||||
currentToast.id = PREFIX + '-toast';
|
||||
currentToast.textContent = message;
|
||||
uiAppend(currentToast);
|
||||
requestAnimationFrame(() => {
|
||||
toastEl.style.opacity = '1';
|
||||
toastEl.style.transform = 'translateX(-50%) translateY(0)';
|
||||
if (toastEl !== currentToast) return;
|
||||
currentToast.style.opacity = '1';
|
||||
currentToast.style.transform = 'translateX(-50%) translateY(0)';
|
||||
});
|
||||
setTimeout(() => {
|
||||
if (toastEl) {
|
||||
toastEl.style.opacity = '0';
|
||||
toastEl.style.transform = 'translateX(-50%) translateY(8px)';
|
||||
setTimeout(() => { if (toastEl) { toastEl.remove(); toastEl = null; } }, 250);
|
||||
}
|
||||
if (toastEl !== currentToast) return;
|
||||
currentToast.style.opacity = '0';
|
||||
currentToast.style.transform = 'translateX(-50%) translateY(8px)';
|
||||
setTimeout(() => {
|
||||
if (toastEl !== currentToast) return;
|
||||
currentToast.remove();
|
||||
toastEl = null;
|
||||
}, 250);
|
||||
}, duration);
|
||||
}
|
||||
|
||||
|
||||
@@ -528,6 +528,34 @@ describe('live-browser.js regression guards', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('toast enter and dismiss timers only touch the current toast element', () => {
|
||||
assert.match(
|
||||
SOURCE,
|
||||
/function showToast\(message, duration\)[\s\S]{0,1200}?const currentToast = el\('div'/,
|
||||
'showToast must capture the created toast in a local so delayed callbacks cannot dereference or remove a stale global toastEl',
|
||||
);
|
||||
assert.match(
|
||||
SOURCE,
|
||||
/requestAnimationFrame\(\(\) => \{[\s\S]{0,80}?if \(toastEl !== currentToast\) return;[\s\S]{0,120}?currentToast\.style\.opacity = '1';/,
|
||||
'toast enter rAF must no-op when dismissToast or a newer toast replaced toastEl before the frame fires',
|
||||
);
|
||||
assert.match(
|
||||
SOURCE,
|
||||
/setTimeout\(\(\) => \{[\s\S]{0,80}?if \(toastEl !== currentToast\) return;[\s\S]{0,120}?currentToast\.style\.opacity = '0';/,
|
||||
'toast auto-dismiss timer must not animate a newer toast created after this timer was scheduled',
|
||||
);
|
||||
assert.match(
|
||||
SOURCE,
|
||||
/setTimeout\(\(\) => \{[\s\S]{0,80}?if \(toastEl !== currentToast\) return;[\s\S]{0,80}?currentToast\.remove\(\);[\s\S]{0,80}?toastEl = null;/,
|
||||
'toast removal timer must only remove and clear the same toast it scheduled',
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
SOURCE,
|
||||
/requestAnimationFrame\(\(\) => \{\s*toastEl\.style/,
|
||||
'toast enter rAF must not read toastEl.style directly after dismissToast can null it',
|
||||
);
|
||||
});
|
||||
|
||||
it('insert mode UI and generate payload guards', () => {
|
||||
assert.match(SOURCE, /function toggleInsert\(\)/, 'global bar must expose insert toggle');
|
||||
assert.match(SOURCE, /PREFIX \+ '-insert-toggle'/, 'insert toggle needs stable id');
|
||||
|
||||
Reference in New Issue
Block a user