Files
Azornes-Comfyui-LayerForge/tests/test_image_loading_characterization.mjs

128 lines
3.8 KiB
JavaScript

import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import test from 'node:test';
import { CanvasState } from '../js/canvas/canvas_state.js';
const [canvasStateSource, maskEditorSource] = await Promise.all([
readFile(new URL('../src/canvas/canvas_state.ts', import.meta.url), 'utf8'),
readFile(new URL('../src/mask/mask_editor_integration.ts', import.meta.url), 'utf8'),
]);
function installImageStub() {
const originalImage = Object.getOwnPropertyDescriptor(globalThis, 'Image');
Object.defineProperty(globalThis, 'Image', {
configurable: true,
value: class TestImage {
constructor() {
this.onload = null;
this.onerror = null;
this.crossOrigin = '';
this.width = 4;
this.height = 3;
}
set src(value) {
this.source = value;
queueMicrotask(() => {
if (value === 'bad-image') {
this.onerror?.(new Error('image decode failed'));
} else {
this.onload?.();
}
});
}
},
});
return () => {
if (originalImage) {
Object.defineProperty(globalThis, 'Image', originalImage);
} else {
delete globalThis.Image;
}
};
}
function installCanvasStub() {
const originalDocument = Object.getOwnPropertyDescriptor(globalThis, 'document');
Object.defineProperty(globalThis, 'document', {
configurable: true,
value: {
createElement(tagName) {
assert.equal(tagName, 'canvas');
return {
getContext() {
return { drawImage() {} };
},
toDataURL() {
return 'bitmap-data';
},
};
},
},
});
return () => {
if (originalDocument) {
Object.defineProperty(globalThis, 'document', originalDocument);
} else {
delete globalThis.document;
}
};
}
test('CanvasState preserves image loading, cross-origin, and failure behavior', async () => {
const restore = installImageStub();
const state = Object.create(CanvasState.prototype);
try {
const loadedLayer = await new Promise(resolve => {
state._createLayerFromSrc({ id: 'layer-1' }, 'good-image', 2, resolve);
});
assert.equal(loadedLayer.image.source, 'good-image');
assert.equal(loadedLayer.image.crossOrigin, 'anonymous');
const failedLayer = await new Promise(resolve => {
state._createLayerFromSrc({ id: 'layer-2' }, 'bad-image', 3, resolve);
});
assert.equal(failedLayer, null);
} finally {
restore();
}
});
test('CanvasState preserves ImageBitmap conversion before shared image loading', async () => {
const restoreImage = installImageStub();
const restoreCanvas = installCanvasStub();
const state = Object.create(CanvasState.prototype);
try {
const loadedLayer = await new Promise(resolve => {
state._createLayerFromSrc({ id: 'layer-bitmap' }, { width: 8, height: 6 }, 4, resolve);
});
assert.equal(loadedLayer.image.source, 'bitmap-data');
assert.equal(loadedLayer.image.crossOrigin, 'anonymous');
} finally {
restoreCanvas();
restoreImage();
}
});
test('CanvasState layer source loading has no local Image construction', () => {
const methodStart = canvasStateSource.indexOf('_createLayerFromSrc(');
const methodEnd = canvasStateSource.indexOf('\n async saveStateToDB', methodStart);
const methodBody = canvasStateSource.slice(methodStart, methodEnd);
assert.equal((methodBody.match(/loadImage\(/g) ?? []).length, 2);
assert.doesNotMatch(methodBody, /new Image\(/);
});
test('Mask editor image loaders preserve their rejection and cleanup policies', () => {
assert.match(maskEditorSource, /return loadImage\(this\.maskTool\.getMask\(\)\.toDataURL\(\)\)/);
assert.match(maskEditorSource, /resultImage = await loadImage\(this\.node\.imgs\[0\]\.src\)/);
assert.match(maskEditorSource, /this\.node\.imgs = \[\]/);
});