mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-29 17:02:28 +00:00
fix: recover completed startup fade cleanup (client-reliability-10)
Extract existing delegated cleanup from index into one cohesive helper and reconcile finished interface CSS animations when initialization arrives after animationend. Preserve active/replacement effects, future end/cancel events, thumbnail eligibility and unrelated classes. No CSS or other bootstrap changes. New17 controlled scenarios including real isolated index cleanup wiring;19distinct mutants killed/all17casescovered, full lint11pass. Existing assertions unchanged. Full gate, build and native point validation still require separate slots.
This commit is contained in:
1 parent
f01ce01259
commit
aff8841694
11 files changed
+318
-54
No files matched your search
@@ -0,0 +1,7 @@
|
||||
# Client10 goal and scope
|
||||
|
||||
- Authority exact dispatch ROUTING-2026-10-27-client10-animation-cleanup.md read; sourcef01ce012 after safe ff, untracked client9 note preserved.
|
||||
- Goal recover already-finished transient interface fade/delay classes during delayed cleanup init; preserve active/future interface animation and thumbnail end/cancel semantics. Existing listener-only cleanup cannot observe an event already dispatched.
|
||||
- Done now: minimal index block extraction + helper, scenario/wiring regressions, killing mutants for all cases, lint under freshhealth. Fullgate/landing/build/nativebrowser are later explicit slots. No other bootstrap/CSS/app edits or oldoverflow closure.
|
||||
- Plan: create/claim10; record contract/testintegrity; extract existing cleanup; reconcile finished own-target CSS interface animations with no stillactive counterpart; test real helper and extracted bootstrap wiring; isolated mutants; lint; namedpathcommit/merge and READY only, no submit.
|
||||
- getAnimations/finished/fill reference https://www.w3.org/TR/web-animations-1/; finished forwards effects remain relevant and can retain stacking context. Avoid sleeps, timers, layout reads, arbitrary phase-duration guesses and broad class stripping.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Client10 decisions
|
||||
|
||||
- Extract only existing animationcleanup block into59line ui/fadeInCleanup helper; index1456→1405lines. No other bootstrap/CSS/app files changed. Existing thumbnail eligibility retained on end/cancel; pseudo effects are excluded from element cleanup.
|
||||
- Install one capture listenerpair then query Document.getAnimations once for already-finished own-target interface CSS animations. Skip running/paused/idle effects and finished effects with same-target live counterpart. No elapsedtime guesses/timers/MutationObserver/layout reads/promises to retain.
|
||||
- Unsupported animation inspection retains event-based cleanup; reducedmotion animation:none retains original hooks without animation effects or new motion. No unsupported-browser late-recovery guarantee. Reference https://www.w3.org/TR/web-animations-1/ describes relevant finished/filling effects and stacking contexts.
|
||||
- Per-document WeakMap makes repeated init idempotent and gives safe explicit disposal/reinstall. Stale disposer cannot erase replacement registration.
|
||||
- Focused scenarios use real helper/JSDOM and controlled CSSAnimation metadata; wiring executes real isolated indexcleanup statements with actual imported helper, not fullapp/bootstrap dependencies. Native CSS animation/hit-testing requires later granted QA. Preserve originaloverflow362/360+394/390 and live/wholeStage3 boundaries.
|
||||
@@ -80,6 +80,8 @@
|
||||
|
||||
## client-reliability
|
||||
|
||||
- 2026-10-27 client10 scoped cleanup READY: late-init recovery extracted from index animation block to59line helper;17newscenarios pass/19distinct killedmutants/all17covered/lint11pass. Existing tests/CSS/app/bootstrap untouched outside block/import. No full/build/browser/submission grant; native360point+390/1280/reducedmotion and normalgate/landing remain. Preserve prior362/360+394/390 overflow and item9live/native limits. Evidence test_logs/TEST_LOG_2026-10-27_01-39-00_client-reliability-10-ready.md.
|
||||
|
||||
- 2026-10-26 19:43UTC: separately granted local build/artifact passes exit0 at7594c54d (runtimeaf61c271); artifact15868a91/indexSHA9c48e0fe, clean tree, no previewserver/deploy. Sandbox build swcompat history probe skipped; prior unchanged-input hostlint11pass retained. Slot RELEASED; native torrent/browser+QA timing need distinct routing, item9 staysopen. test_logs/TEST_LOG_2026-10-26_19-43-00_client-reliability-9-build.md.
|
||||
|
||||
- 2026-10-26 19:41UTC: item9 active-torrent product LANDED af61c271 via normal eafa364d queue gate exit0; independent438discovered/438unique starts/missingextra0 and ancestry0. Complete log/result/discovery hashed under state/evidence/client9-torrent-validation-20261026, prior rejected275partial intact. lint11/savedfocused60/mutants19 retained. Normalgate slot RELEASED; separate build/native-browser routing remains, item9 open. No real peers/byte teardown/wholeStage3 claim. See test_logs/TEST_LOG_2026-10-26_19-41-00_client-reliability-9-torrent-landed.md.
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"id": "client-reliability-10",
|
||||
"area": "client-reliability",
|
||||
"status": "in-progress",
|
||||
"title": "Recover completed startup fade animation after delayed cleanup initialization",
|
||||
"blockedOn": null,
|
||||
"addedOn": "2026-10-27",
|
||||
"filedAt": "f01ce012595d74abac3c8adca7c8eec219294c7d",
|
||||
"brief": "/home/user/Documents/GitHub/bitvid-dispatch/docs/agents/ROUTING-2026-10-27-client10-animation-cleanup.md",
|
||||
"evidence": "Dispatch independently measured actualapp360 animationend514.8ms before listener3245.7ms; samepointASIDEfailure\u2192onlyapp.fade-in removalINPUTtoggle, geometry unchanged. Relevantinputdiff0 vs2754a8af/currentf01ce012; PRcheck0 attributed dispatch. Explicit bounded indexcleanup+optionalhelper/focusedtests; noCSS/app/otherbootstrap. Focused/mutants/lint only; full/build/browser/submit awaitREADY+slot. Minimal cleanup extracted at f01ce012: index1405/helper59, new17scenarios17pass/fail0/cancelled0;19distinctisolatedmutantsallkilled/all17casesmapped;hostlint11pass, diffcheck0. Noexistingtestedits/full/build/browser/submission. Evidence test_logs/TEST_LOG_2026-10-27_01-39-00_client-reliability-10-ready.md; sourcewiring scenario executes actualisolatedindexsection, notfullapp; nativeCSS/hittesting remains later."
|
||||
}
|
||||
+3
-54
@@ -56,6 +56,7 @@ import { relayManager } from "./relayManager.js";
|
||||
import createApplication from "./bootstrap.js";
|
||||
import SearchFilterModal from "./ui/components/SearchFilterModal.js";
|
||||
import { initializeSiteUpdateNotice } from "./ui/siteUpdateNotice.js";
|
||||
import { initializeFadeInCleanup } from "./ui/fadeInCleanup.js";
|
||||
import { installTestHarness } from "./testHarness.js";
|
||||
|
||||
validateInstanceConfig();
|
||||
@@ -497,61 +498,9 @@ if (typeof window !== "undefined" && typeof window.addEventListener === "functio
|
||||
window.addEventListener("pageshow", handlePageShow);
|
||||
}
|
||||
|
||||
const INTERFACE_FADE_IN_ANIMATION = "interface-fade-in";
|
||||
const VIDEO_THUMBNAIL_FADE_IN_ANIMATION = "video-thumbnail-fade-in";
|
||||
|
||||
//
|
||||
// Centralized animation cleanup
|
||||
// ------------------------------
|
||||
// We attach a single capture-phase listener for animation events so that any
|
||||
// chrome element or thumbnail using our fade-in helpers can automatically shed
|
||||
// the temporary classes/data attributes that trigger the transition. This is
|
||||
// critical because these DOM nodes frequently persist while the surrounding
|
||||
// lists re-render; if the classes stick around, future layout shuffles would
|
||||
// replay the fade and make the UI flicker. Clearing the hooks immediately after
|
||||
// the first animation keeps the graceful entrance without introducing future
|
||||
// flashes.
|
||||
const handleFadeInAnimationComplete = (event) => {
|
||||
const { animationName, target } = event;
|
||||
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (animationName === INTERFACE_FADE_IN_ANIMATION) {
|
||||
if (!target.classList.contains("fade-in")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the transient fade-in class so future renders keep the element
|
||||
// visible. The delays piggyback on `fade-in`, so we strip those too.
|
||||
target.classList.remove("fade-in");
|
||||
Array.from(target.classList).forEach((className) => {
|
||||
if (className.startsWith("fade-in-delay-")) {
|
||||
target.classList.remove(className);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (animationName !== VIDEO_THUMBNAIL_FADE_IN_ANIMATION) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
target.dataset &&
|
||||
target.dataset.videoThumbnail === "true" &&
|
||||
target.dataset.thumbnailLoaded === "true"
|
||||
) {
|
||||
// Thumbnails use the same pattern: once the fade finishes we clear the flag
|
||||
// so a later `load` event (or template reuse) does not restart the
|
||||
// animation.
|
||||
delete target.dataset.thumbnailLoaded;
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("animationend", handleFadeInAnimationComplete, true);
|
||||
document.addEventListener("animationcancel", handleFadeInAnimationComplete, true);
|
||||
// Recover completed startup fades as well as future animation events.
|
||||
initializeFadeInCleanup(document);
|
||||
|
||||
// 1) Load modals (login, application, etc.)
|
||||
const fetchPartial = async (url) => {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
const INTERFACE_ANIMATION = "interface-fade-in";
|
||||
const THUMBNAIL_ANIMATION = "video-thumbnail-fade-in";
|
||||
const registrations = new WeakMap();
|
||||
|
||||
function clearInterfaceFade(target) {
|
||||
if (!target.classList.contains("fade-in")) return;
|
||||
target.classList.remove("fade-in");
|
||||
for (const className of Array.from(target.classList)) {
|
||||
if (className.startsWith("fade-in-delay-")) target.classList.remove(className);
|
||||
}
|
||||
}
|
||||
|
||||
// Install before reconciling: initial CSS animation events can precede the
|
||||
// application module's evaluation. Finished forwards-fill effects still appear
|
||||
// in getAnimations() and retain their stacking context until the class is gone.
|
||||
export function initializeFadeInCleanup(doc = document) {
|
||||
if (registrations.has(doc)) return registrations.get(doc);
|
||||
const isElement = (target) => target instanceof doc.defaultView.HTMLElement;
|
||||
const onComplete = ({ animationName, target, pseudoElement }) => {
|
||||
if (!isElement(target) || pseudoElement) return;
|
||||
if (animationName === INTERFACE_ANIMATION) {
|
||||
clearInterfaceFade(target);
|
||||
} else if (
|
||||
animationName === THUMBNAIL_ANIMATION &&
|
||||
target.dataset.videoThumbnail === "true" &&
|
||||
target.dataset.thumbnailLoaded === "true"
|
||||
) {
|
||||
delete target.dataset.thumbnailLoaded;
|
||||
}
|
||||
};
|
||||
doc.addEventListener("animationend", onComplete, true);
|
||||
doc.addEventListener("animationcancel", onComplete, true);
|
||||
const dispose = () => {
|
||||
doc.removeEventListener("animationend", onComplete, true);
|
||||
doc.removeEventListener("animationcancel", onComplete, true);
|
||||
if (registrations.get(doc) === dispose) registrations.delete(doc);
|
||||
};
|
||||
registrations.set(doc, dispose);
|
||||
|
||||
// No duration guesses or timers: running, delayed and paused entrances must
|
||||
// keep their classes. Unsupported animation inspection retains event cleanup.
|
||||
const animations = typeof doc.getAnimations === "function" ? doc.getAnimations() : [];
|
||||
const interfaceAnimations = animations.filter((animation) =>
|
||||
animation.animationName === INTERFACE_ANIMATION &&
|
||||
isElement(animation.effect?.target) && !animation.effect.pseudoElement
|
||||
);
|
||||
for (const animation of interfaceAnimations) {
|
||||
const target = animation.effect.target;
|
||||
if (
|
||||
animation.playState === "finished" &&
|
||||
!interfaceAnimations.some((other) =>
|
||||
other.effect.target === target && other.playState !== "finished"
|
||||
)
|
||||
) {
|
||||
clearInterfaceFade(target);
|
||||
}
|
||||
}
|
||||
return dispose;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
# Client10 transient startup cleanup contract
|
||||
|
||||
Authority: exact dispatch ROUTING-2026-10-27-client10-animation-cleanup.md. Existing transient interface cleanup is intended after animation end/cancel; late initialization currently misses already-delivered events. Extend initialization to reconcile finished own-target interface CSS animations, while keeping current/future active animations, unrelated effects and thumbnail event rule. No existing tests/expectations changed. Reduced-motion animation:none does not acquire a new animation or timer.
|
||||
|
||||
```yaml
|
||||
test_integrity_note:
|
||||
change_type: [new_tests]
|
||||
scenarios:
|
||||
- id: SCN-late-interface-cleanup
|
||||
given: interface CSS animation finished before initializer
|
||||
when: actual cleanup initializer or real index cleanup wiring executes
|
||||
then: fade-in and all fade-in-delay hooks removed, unrelated classes preserved
|
||||
- id: SCN-live-future-animation
|
||||
given: running pending paused or future entrance and unrelated animations
|
||||
when: initializer and genuine completion or cancellation events occur
|
||||
then: live effects preserved until completion, unrelated/pseudo effects do not trigger interface cleanup
|
||||
- id: SCN-thumbnail-and-registration
|
||||
given: loaded or unloaded thumbnail and repeated cleanup registration
|
||||
when: matching event, repeated init, disposal and reinstall occur
|
||||
then: existing thumbnail eligibility respected, single listener pair and deterministic teardown
|
||||
observable_outcomes: [DOM classes and dataset, listener lifecycle, actual bootstrap cleanup call]
|
||||
determinism_controls: [JSDOM DOM and events, explicit CSSAnimation-state boundary, no public IO or elapsed-time waits]
|
||||
anti_cheat_rationale:
|
||||
prevents: [unconditional class stripping, unrelated-animation cleanup, missing bootstrap call, duplicate listener retention]
|
||||
relaxation:
|
||||
did_relax_any_assertion: false
|
||||
if_true_explain_spec_basis: ''
|
||||
```
|
||||
|
||||
Browser geometry/hit testing, real CSSAnimation state, original overflow and real-device qualification are not established by these controlled offline checks.
|
||||
|
||||
Validation:17/17newscenarios pass;19distinct isolated mutants naturally fail withcancelled0, all17cases covered. Exact maps/logs state/evidence/client10-animation-cleanup-20261027/mutants-summary.json and mutants-coverage.json. Existing tests unchanged. Full/browser/build/landing pending.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Client10 focused candidate READY
|
||||
|
||||
- Exact dispatch routing read, safe ff tof01ce012; relevant inputs unchanged from independently reproduced2754a8af. No baseline/browser repeat. PublicPR0 is dispatch-attributed evidence. Item10 created/claimed, client9note preserved.
|
||||
- Source scoped js/index.js cleanup block+js/ui/fadeInCleanup.js only; unique new interface-fade-cleanup.test.mjs. No existing test edits/weakening. Integrity contract written before tests.
|
||||
- Freshhealth01:36:12 OK/CPU50C/load0.57/RAM68.2%/swap-in0. `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD node tests/interface-fade-cleanup.test.mjs > state/evidence/client10-animation-cleanup-20261027/focused-0136.log 2>&1`: naturalexit0,17tests/17pass/0fail/0cancelled/0skip (~137ms TAPduration).
|
||||
- Fresh01:37:17 OK/CPU48C/load0.41/RAM68.3%/swap-in0. `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD python3 state/evidence/client10-animation-cleanup-20261027/run-mutants.py > .../mutants-0138.log 2>&1`: runnernaturalexit0; all19distinctvariants eachnaturalexit1/fail>0/cancelled0,17/17scenario names have killingvariant. Mutants only isolatedcopies; worktree never modified/restored. Full summary and per-case map mutants-summary.json/mutants-coverage.json. Runner durable state/deps/client10-animation-cleanup, depsmanifest updated.
|
||||
- Fresh01:37:50 OK/CPU51.1C/load0.59/RAM68%/swap-in0. Approved host `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD npm run lint > .../lint-host-0138.log 2>&1`: naturalexit0/all11checks. Known sandbox gitgrepEPERM avoided via normal approval; no lint baseline/config changes.
|
||||
- `git diff --check`: exit0. Product index1456→1405lines/helper59; test177lines. Complete source/test snapshots/logs/mutants/manifest saved state/evidence/client10-animation-cleanup-20261027.
|
||||
- No fullunit/build/browser/submission/landing. Controlled offline test models Animation state; real CSSAnimation enumeration/nativepoint360+390/1280/reducedmotion/realdevice unverified. Originaloverflow remains separate; item9 native/live/CPU/wholeStage3 limits retained.
|
||||
@@ -0,0 +1,177 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { JSDOM } from "jsdom";
|
||||
import { initializeFadeInCleanup } from "../js/ui/fadeInCleanup.js";
|
||||
|
||||
function fixture(t) {
|
||||
const dom = new JSDOM('<!doctype html><body><main id="app" class="fade-in fade-in-delay-100 fade-in-delay-300 keep"></main></body>');
|
||||
const doc = dom.window.document;
|
||||
const app = doc.getElementById("app");
|
||||
const errors = [];
|
||||
dom.window.addEventListener("error", event => {
|
||||
errors.push(event.error); event.preventDefault();
|
||||
});
|
||||
const animations = [];
|
||||
const listeners = new Map();
|
||||
const add = doc.addEventListener.bind(doc), remove = doc.removeEventListener.bind(doc);
|
||||
doc.addEventListener = (type, fn, capture) => {
|
||||
if (type.startsWith("animation")) {
|
||||
assert.equal(capture, true, "cleanup must capture descendant events");
|
||||
if (!listeners.has(type)) listeners.set(type, new Set());
|
||||
listeners.get(type).add(fn);
|
||||
}
|
||||
add(type, fn, capture);
|
||||
};
|
||||
doc.removeEventListener = (type, fn, capture) => {
|
||||
listeners.get(type)?.delete(fn); remove(type, fn, capture);
|
||||
};
|
||||
doc.getAnimations = () => [...animations];
|
||||
function animation(target = app, playState = "finished", name = "interface-fade-in", pseudoElement = null) {
|
||||
const value = { animationName: name, playState, effect: { target, pseudoElement } };
|
||||
animations.push(value); return value;
|
||||
}
|
||||
function event(target = app, name = "interface-fade-in", type = "animationend", pseudoElement = "") {
|
||||
const value = new dom.window.Event(type, { bubbles: true });
|
||||
Object.assign(value, { animationName: name, pseudoElement });
|
||||
target.dispatchEvent(value);
|
||||
}
|
||||
function thumbnail(loaded = "true", marked = "true") {
|
||||
const el = doc.createElement("img");
|
||||
el.dataset.videoThumbnail = marked; el.dataset.thumbnailLoaded = loaded;
|
||||
doc.body.append(el); return el;
|
||||
}
|
||||
function cleaned(el = app) {
|
||||
assert.equal(el.classList.contains("fade-in"), false);
|
||||
assert.equal([...el.classList].some(x => x.startsWith("fade-in-delay-")), false);
|
||||
assert.equal(el.classList.contains("keep"), true);
|
||||
}
|
||||
function retained(el = app) {
|
||||
assert.equal(el.classList.contains("fade-in"), true);
|
||||
assert.equal(el.classList.contains("fade-in-delay-100"), true);
|
||||
assert.equal(el.classList.contains("fade-in-delay-300"), true);
|
||||
}
|
||||
t.after(() => {
|
||||
initializeFadeInCleanup(doc)(); dom.window.close();
|
||||
assert.deepEqual(errors, [], "cleanup events must not throw");
|
||||
});
|
||||
return { doc, app, animations, animation, event, thumbnail, cleaned, retained, listeners };
|
||||
}
|
||||
|
||||
test("late initialization recovers finished entrance and all delay hooks", t => {
|
||||
const f = fixture(t); f.animation(); initializeFadeInCleanup(f.doc); f.cleaned();
|
||||
});
|
||||
|
||||
for (const state of ["running", "paused", "idle"]) {
|
||||
test(`initialization preserves ${state} entrance until completion`, t => {
|
||||
const f = fixture(t); f.animation(f.app, state);
|
||||
initializeFadeInCleanup(f.doc); f.retained();
|
||||
f.event(); f.cleaned();
|
||||
});
|
||||
}
|
||||
|
||||
test("finished older effect cannot remove a same-target active replacement", t => {
|
||||
const f = fixture(t); f.animation(); f.animation(f.app, "running");
|
||||
initializeFadeInCleanup(f.doc); f.retained();
|
||||
f.event(); f.cleaned();
|
||||
});
|
||||
|
||||
test("unrelated finished animation preserves entrance hooks", t => {
|
||||
const f = fixture(t); f.animation(f.app, "finished", "spinner");
|
||||
initializeFadeInCleanup(f.doc); f.retained();
|
||||
f.event(f.app, "spinner"); f.retained();
|
||||
});
|
||||
|
||||
test("pseudo-element finished effects and events do not clear the element", t => {
|
||||
const f = fixture(t); f.animation(f.app, "finished", "interface-fade-in", "::before");
|
||||
initializeFadeInCleanup(f.doc); f.retained();
|
||||
f.event(f.app, "interface-fade-in", "animationend", "::before"); f.retained();
|
||||
});
|
||||
|
||||
test("finished child does not clear an ancestor with an active entrance", t => {
|
||||
const f = fixture(t); const child = f.doc.createElement("div");
|
||||
child.className = f.app.className; f.app.append(child);
|
||||
f.animation(f.app, "running"); f.animation(child);
|
||||
initializeFadeInCleanup(f.doc); f.retained(); f.cleaned(child);
|
||||
});
|
||||
|
||||
test("future dynamically inserted entrance cleans on delegated end", t => {
|
||||
const f = fixture(t); initializeFadeInCleanup(f.doc);
|
||||
const later = f.doc.createElement("div"); later.className = f.app.className;
|
||||
f.doc.body.append(later); f.retained(later); f.event(later); f.cleaned(later);
|
||||
});
|
||||
|
||||
test("active entrance cancellation cleans the same transient hooks", t => {
|
||||
const f = fixture(t); f.animation(f.app, "running"); initializeFadeInCleanup(f.doc);
|
||||
f.retained(); f.event(f.app, "interface-fade-in", "animationcancel"); f.cleaned();
|
||||
});
|
||||
|
||||
test("thumbnail end and cancellation preserve existing loaded eligibility", t => {
|
||||
const f = fixture(t); initializeFadeInCleanup(f.doc);
|
||||
for (const type of ["animationend", "animationcancel"]) {
|
||||
const loaded = f.thumbnail(), pending = f.thumbnail("false"), unrelated = f.thumbnail("true", "false");
|
||||
for (const el of [loaded, pending, unrelated]) f.event(el, "video-thumbnail-fade-in", type);
|
||||
assert.equal(loaded.hasAttribute("data-thumbnail-loaded"), false);
|
||||
assert.equal(pending.dataset.thumbnailLoaded, "false");
|
||||
assert.equal(unrelated.dataset.thumbnailLoaded, "true");
|
||||
}
|
||||
f.retained();
|
||||
});
|
||||
|
||||
test("interface completion preserves absent fade-in and unrelated delay classes", t => {
|
||||
const f = fixture(t); f.app.classList.remove("fade-in"); initializeFadeInCleanup(f.doc);
|
||||
f.event();
|
||||
assert.equal(f.app.className, "fade-in-delay-100 fade-in-delay-300 keep");
|
||||
});
|
||||
|
||||
test("repeated initialization shares one capture listener pair and disposes idempotently", t => {
|
||||
const f = fixture(t); const first = initializeFadeInCleanup(f.doc);
|
||||
assert.equal(initializeFadeInCleanup(f.doc), first);
|
||||
assert.equal(f.listeners.get("animationend").size, 1);
|
||||
assert.equal(f.listeners.get("animationcancel").size, 1);
|
||||
first(); first();
|
||||
assert.equal(f.listeners.get("animationend").size, 0);
|
||||
assert.equal(f.listeners.get("animationcancel").size, 0);
|
||||
f.event(); f.retained();
|
||||
const next = initializeFadeInCleanup(f.doc); assert.notEqual(next, first);
|
||||
first(); // A stale disposer must not erase the current registration.
|
||||
assert.equal(initializeFadeInCleanup(f.doc), next);
|
||||
f.event(); f.cleaned();
|
||||
});
|
||||
|
||||
test("missing animation inspection retains event cleanup without guessing completion", t => {
|
||||
const f = fixture(t); delete f.doc.getAnimations;
|
||||
initializeFadeInCleanup(f.doc); f.retained(); f.event(); f.cleaned();
|
||||
});
|
||||
|
||||
test("reduced-motion no-animation initialization preserves hooks and thumbnail eligibility", t => {
|
||||
const f = fixture(t); const img = f.thumbnail();
|
||||
f.doc.defaultView.matchMedia = query => ({ matches: query === "(prefers-reduced-motion: reduce)" });
|
||||
initializeFadeInCleanup(f.doc); f.retained();
|
||||
assert.equal(img.dataset.thumbnailLoaded, "true");
|
||||
assert.equal(f.animations.length, 0);
|
||||
});
|
||||
|
||||
test("non-element animation events do not interrupt future cleanup", t => {
|
||||
const f = fixture(t); initializeFadeInCleanup(f.doc);
|
||||
f.event(f.doc); f.retained(); f.event(); f.cleaned();
|
||||
});
|
||||
|
||||
test("real index cleanup wiring recovers a fade whose end preceded bootstrap evaluation", async t => {
|
||||
const f = fixture(t); f.animation();
|
||||
f.event(); f.retained(); // End occurred before production registration.
|
||||
const index = await readFile(new URL("../js/index.js", import.meta.url), "utf8");
|
||||
const imported = index.match(/import\s*\{\s*(\w+)\s*\}\s*from\s*["'](.+fadeInCleanup\.js)["'];/);
|
||||
assert.ok(imported, "index must import the cleanup initializer");
|
||||
const actual = await import(new URL(imported[2], new URL("../js/index.js", import.meta.url)));
|
||||
const start = index.indexOf("// Centralized animation cleanup");
|
||||
const end = index.indexOf("// 1) Load modals", start);
|
||||
assert.ok(start >= 0 && end > start, "execute actual isolated bootstrap cleanup section");
|
||||
const cleanupSection = index.slice(start, end);
|
||||
// Real bootstrap statements and the real exported initializer; other app IO
|
||||
// is outside this focused wiring scenario, not replaced by a pretend boot.
|
||||
new Function("document", imported[1], cleanupSection)(f.doc, actual[imported[1]]);
|
||||
f.cleaned();
|
||||
const future = f.doc.createElement("div"); future.className = "fade-in fade-in-delay-100 keep";
|
||||
f.doc.body.append(future); f.event(future); f.cleaned(future);
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
- [x] Read exactrouting/rules/knownissues/testintegrity/logging; sourcef01ce012 and preserveclient9note.
|
||||
- [ ] Claim10 and write integrity/contract.
|
||||
- [ ] Extract minimal cleanup+finishedanimation recovery, real wiring regressions.
|
||||
- [ ] Focused+deliberate mutants+lint serial with freshhealth.
|
||||
- [ ] Namedpathcommit/merge, evidence and READY; no full/build/browser/submit yet.
|
||||
@@ -0,0 +1,6 @@
|
||||
- [x] Exactrouting+claim10/sourcef01ce012; no repeated baseline/client9checks.
|
||||
- [x] Minimal indexblock/helper extraction and17faithful scenarios including real index section.
|
||||
- [x] All19distinctmutantskilled/all17casescovered; full lint11pass.
|
||||
- [x] Durable snapshots/logs/mutant runner+deps manifest and integrity notes.
|
||||
- [ ] Namedpathcommit then merge currentunstable; input comparison and READY review.
|
||||
- [ ] Explicit fullgate/build/browser/submission grants and durablelanding/ancestry; no current grant.
|
||||
Reference in new issue
Block a user