feat: scaffold projet avec stubs et test runner navigateur

This commit is contained in:
2026-05-27 10:05:07 +02:00
commit 4e9640ddfa
9 changed files with 1758 additions and 0 deletions
+181
View File
@@ -0,0 +1,181 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<title>Tests — Convertisseur Images</title>
<style>
body { font-family: monospace; padding: 20px; background: #f9f9f9; }
.pass { color: green; margin: 2px 0; }
.fail { color: red; font-weight: bold; margin: 2px 0; }
h2 { margin-top: 20px; border-top: 1px solid #ddd; padding-top: 10px; }
#summary { font-size: 1.2em; font-weight: bold; margin-top: 24px; padding: 12px;
border-radius: 8px; display: inline-block; }
</style>
</head>
<body>
<h1>Tests unitaires</h1>
<div id="results"></div>
<div id="summary"></div>
<script type="module">
let passed = 0, failed = 0;
function assert(description, condition) {
const ok = Boolean(condition);
ok ? passed++ : failed++;
const el = document.createElement('div');
el.className = ok ? 'pass' : 'fail';
el.textContent = `${ok ? '✅' : '❌'} ${description}`;
document.getElementById('results').appendChild(el);
}
function section(title) {
const el = document.createElement('h2');
el.textContent = title;
document.getElementById('results').appendChild(el);
}
// Helper: construit un ImageData-like depuis un tableau 2D de [R,G,B]
function makeImageData(grid) {
const h = grid.length, w = grid[0].length;
const data = new Uint8ClampedArray(w * h * 4);
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const i = (y * w + x) * 4;
const [r, g, b] = grid[y][x];
data[i] = r; data[i+1] = g; data[i+2] = b; data[i+3] = 255;
}
}
return { data, width: w, height: h };
}
// ── heicConverter ──────────────────────────────────────────────
import { convertHeicToJpeg } from './js/heicConverter.js';
section('heicConverter');
try {
await convertHeicToJpeg(null);
assert('convertHeicToJpeg(null) lance TypeError', false);
} catch (e) {
assert('convertHeicToJpeg(null) lance TypeError', e instanceof TypeError);
}
try {
const pngFile = new File([new Uint8Array([137,80,78,71])], 'test.png', { type: 'image/png' });
await convertHeicToJpeg(pngFile);
assert('convertHeicToJpeg avec PNG lance TypeError', false);
} catch (e) {
assert('convertHeicToJpeg avec PNG lance TypeError', e instanceof TypeError);
}
// ── bgRemover — chebyshevDistance ──────────────────────────────
import { chebyshevDistance, createFloodFillMask, applyMaskToImageData } from './js/bgRemover.js';
section('bgRemover — chebyshevDistance');
assert('couleurs identiques → distance 0',
chebyshevDistance([255,255,255], [255,255,255]) === 0);
assert('blanc vs quasi-blanc (diff max=10) → distance 10',
chebyshevDistance([255,255,255], [245,250,252]) === 10);
assert('blanc vs gris 128 → distance 127',
chebyshevDistance([255,255,255], [128,128,128]) === 127);
assert('distance = max des écarts par canal',
chebyshevDistance([200,100,50], [180,90,20]) === 30);
// ── bgRemover — createFloodFillMask ────────────────────────────
section('bgRemover — createFloodFillMask');
const W = [255,255,255], R = [200,50,50], B = [50,50,200];
const img3x3 = makeImageData([
[W, W, W],
[W, R, W],
[W, W, W],
]);
const mask3x3 = createFloodFillMask(img3x3, [255,255,255], 30);
assert('3×3 : 8 pixels blancs de bordure masqués (somme=8)',
Array.from(mask3x3).reduce((a,b) => a+b, 0) === 8);
assert('3×3 : pixel rouge central NON masqué',
mask3x3[1 * 3 + 1] === 0);
const img4x4 = makeImageData([
[W, W, W, W],
[W, B, B, W],
[W, B, B, W],
[W, W, W, W],
]);
const mask4x4 = createFloodFillMask(img4x4, [255,255,255], 30);
assert('4×4 : 12 pixels blancs masqués',
Array.from(mask4x4).reduce((a,b) => a+b, 0) === 12);
assert('4×4 : 4 pixels bleus intérieurs non masqués',
mask4x4[1*4+1] === 0 && mask4x4[1*4+2] === 0 &&
mask4x4[2*4+1] === 0 && mask4x4[2*4+2] === 0);
// ── bgRemover — applyMaskToImageData ───────────────────────────
section('bgRemover — applyMaskToImageData');
const imgApply = makeImageData([
[[200,50,50], [50,50,200]],
[[50,50,200], [50,50,200]],
]);
const maskApply = new Uint8Array([1, 0, 0, 0]);
applyMaskToImageData(imgApply, maskApply, [255,255,255]);
const d = imgApply.data;
assert('pixel masqué → blanc pur',
d[0] === 255 && d[1] === 255 && d[2] === 255);
assert('pixel non masqué → inchangé (bleu)',
d[4] === 50 && d[5] === 50 && d[6] === 200);
// ── imageProcessor — processBatch ──────────────────────────────
import { processBatch } from './js/imageProcessor.js';
section('imageProcessor — processBatch');
const resultsA = [];
await processBatch([1,2,3,4,5], async (item) => { resultsA.push(item * 2); }, 3);
assert('processBatch traite les 5 éléments', resultsA.length === 5);
assert('processBatch retourne les bons résultats',
[2,4,6,8,10].every(v => resultsA.includes(v)));
let concurrent = 0, maxConcurrent = 0;
await processBatch([1,2,3,4,5,6], async () => {
concurrent++;
maxConcurrent = Math.max(maxConcurrent, concurrent);
await new Promise(r => setTimeout(r, 10));
concurrent--;
}, 3);
assert('processBatch ne dépasse jamais 3 en simultané', maxConcurrent <= 3);
// ── zipExporter — downloadAllAsZip ─────────────────────────────
import { downloadAllAsZip } from './js/zipExporter.js';
section('zipExporter — downloadAllAsZip');
let capturedHref = null;
const origAppend = document.body.appendChild.bind(document.body);
const origRemove = document.body.removeChild.bind(document.body);
document.body.appendChild = (el) => {
if (el.tagName === 'A') { capturedHref = el.href; el.click = () => {}; return el; }
return origAppend(el);
};
document.body.removeChild = (el) => { try { origRemove(el); } catch {} };
await downloadAllAsZip([
{ name: '12345.jpg', blob: new Blob(['data1'], { type: 'image/jpeg' }) },
{ name: '67890.jpg', blob: new Blob(['data2'], { type: 'image/jpeg' }) },
]);
document.body.appendChild = origAppend;
document.body.removeChild = origRemove;
assert('downloadAllAsZip génère une URL blob:',
capturedHref !== null && capturedHref.startsWith('blob:'));
// ── Résumé ─────────────────────────────────────────────────────
const summary = document.getElementById('summary');
summary.textContent = `${passed} passé(s), ${failed} échoué(s)`;
summary.style.background = failed === 0 ? '#dcfce7' : '#fee2e2';
summary.style.color = failed === 0 ? '#166534' : '#991b1b';
</script>
</body>
</html>