mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-03 01:00:48 +00:00
Add opt-in browser download helpers that own detached links and object URLs, clean up failed or abandoned preparation, and register delayed URL cleanup before issuing a click. Applications retain filename, MIME type, pacing, and user-activation policy. Include browser lifecycle and failure-path tests, public API documentation, and Sonar test-source classification. All 32 PR checks passed; targeted WASM Clippy and formatting passed in local validation.
43 lines
2.4 KiB
JavaScript
43 lines
2.4 KiB
JavaScript
// Test-only browser fault injection; no app runtime depends on this module.
|
|
let originals;
|
|
let created, revoked, clicked, scheduled, lastName, lastMime, timerBeforeClick;
|
|
export function setup(mode) {
|
|
created = 0; revoked = 0; clicked = 0; scheduled = 0;
|
|
lastName = ''; lastMime = ''; timerBeforeClick = false;
|
|
originals = {
|
|
create: URL.createObjectURL, revoke: URL.revokeObjectURL,
|
|
click: HTMLAnchorElement.prototype.click,
|
|
append: Node.prototype.appendChild, timer: window.setTimeout,
|
|
element: Document.prototype.createElement,
|
|
attribute: Element.prototype.setAttribute,
|
|
};
|
|
URL.createObjectURL = function(blob) { created++; lastMime = blob.type; return originals.create.call(URL, blob); };
|
|
URL.revokeObjectURL = function(url) { revoked++; return originals.revoke.call(URL, url); };
|
|
HTMLAnchorElement.prototype.click = function() {
|
|
clicked++; lastName = this.download; timerBeforeClick = scheduled > 0;
|
|
};
|
|
window.setTimeout = function(...args) {
|
|
if (mode === 'timer-error') throw new Error('timer failed');
|
|
scheduled++;
|
|
return originals.timer.apply(window, args);
|
|
};
|
|
if (mode === 'attach-error') Node.prototype.appendChild = function() { throw new Error('attachment failed'); };
|
|
if (mode === 'element-error') Document.prototype.createElement = function() { throw new Error('element failed'); };
|
|
if (mode === 'style-error') Element.prototype.setAttribute = function() { throw new Error('style failed'); };
|
|
}
|
|
export function restore() {
|
|
URL.createObjectURL = originals.create; URL.revokeObjectURL = originals.revoke;
|
|
HTMLAnchorElement.prototype.click = originals.click;
|
|
Node.prototype.appendChild = originals.append; window.setTimeout = originals.timer;
|
|
Document.prototype.createElement = originals.element;
|
|
Element.prototype.setAttribute = originals.attribute;
|
|
}
|
|
export function created_count() { return created; }
|
|
export function revoked_count() { return revoked; }
|
|
export function clicked_count() { return clicked; }
|
|
export function anchor_count() { return document.querySelectorAll('a[download]').length; }
|
|
export function last_name() { return lastName; }
|
|
export function last_mime() { return lastMime; }
|
|
export function timer_before_click() { return timerBeforeClick; }
|
|
export function wait_for_cleanup() { return new Promise(resolve => originals.timer.call(window, resolve, 50)); }
|