Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <title>IconClip Static Demo — unit tests</title> | |
| <style> | |
| body { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; | |
| padding: 24px; max-width: 880px; margin: 0 auto; line-height: 1.5; } | |
| h1 { font-size: 18px; margin: 0 0 12px; } | |
| .pass { color: #1a7f37; } | |
| .fail { color: #c01717; font-weight: 600; } | |
| .summary { padding: 10px 14px; border-radius: 8px; margin: 14px 0; | |
| background: #f3f6fa; border: 1px solid #d6dae3; } | |
| ol { padding-left: 22px; } | |
| li { margin: 4px 0; } | |
| code { background: #f3f6fa; padding: 1px 4px; border-radius: 4px; } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>IconClip static-demo unit tests</h1> | |
| <div id="summary" class="summary">Running…</div> | |
| <ol id="log"></ol> | |
| <script type="module"> | |
| // Note: imports are relative — works because both files ship together. | |
| import { l2NormaliseInPlace, dot, scoreAll, topK } from './js/cosine.js'; | |
| import { buildMask, parseId } from './js/filters.js'; | |
| const log = document.getElementById('log'); | |
| const summary = document.getElementById('summary'); | |
| let pass = 0, fail = 0; | |
| function t(name, fn) { | |
| const li = document.createElement('li'); | |
| try { | |
| fn(); | |
| li.innerHTML = `<span class="pass">PASS</span> — ${name}`; | |
| pass++; | |
| } catch (e) { | |
| li.innerHTML = `<span class="fail">FAIL</span> — ${name}: <code>${escape(e.message)}</code>`; | |
| fail++; | |
| console.error(name, e); | |
| } | |
| log.appendChild(li); | |
| } | |
| function eq(a, b, msg) { | |
| if (Math.abs(a - b) > 1e-5) throw new Error((msg || 'expected equality') + ` (got ${a}, expected ${b})`); | |
| } | |
| function ok(cond, msg) { if (!cond) throw new Error(msg || 'expected truthy'); } | |
| function escape(s) { return String(s).replace(/[<>&]/g, c => ({'<':'<','>':'>','&':'&'}[c])); } | |
| // ----- cosine.js ---------------------------------------------------------- | |
| t('l2NormaliseInPlace makes the vector unit-length', () => { | |
| const v = new Float32Array([3, 0, 4]); | |
| l2NormaliseInPlace(v); | |
| eq(v[0], 0.6); eq(v[1], 0); eq(v[2], 0.8); | |
| // norm == 1 | |
| let s = 0; for (const x of v) s += x * x; | |
| eq(Math.sqrt(s), 1); | |
| }); | |
| t('l2NormaliseInPlace tolerates the zero vector', () => { | |
| const v = new Float32Array([0, 0, 0]); | |
| l2NormaliseInPlace(v); | |
| eq(v[0], 0); eq(v[1], 0); eq(v[2], 0); | |
| }); | |
| t('dot product matches naive implementation (length 4)', () => { | |
| const a = new Float32Array([1, 2, 3, 4]); | |
| const b = new Float32Array([4, 3, 2, 1]); | |
| eq(dot(a, b), 20); | |
| }); | |
| t('dot product handles non-multiple-of-4 lengths (length 7)', () => { | |
| const a = new Float32Array([1, 1, 1, 1, 1, 1, 1]); | |
| const b = new Float32Array([1, 2, 3, 4, 5, 6, 7]); | |
| eq(dot(a, b), 28); | |
| }); | |
| t('scoreAll without mask scores every row', () => { | |
| // 3 rows × 2 dims, row-major | |
| const M = new Float32Array([1, 0, 0, 1, 1, 1]); | |
| const q = new Float32Array([1, 0]); | |
| const s = scoreAll(M, q, 2, null); | |
| eq(s[0], 1); eq(s[1], 0); eq(s[2], 1); | |
| }); | |
| t('scoreAll with mask drops filtered rows to -Infinity', () => { | |
| const M = new Float32Array([1, 0, 0, 1, 1, 1]); | |
| const q = new Float32Array([1, 0]); | |
| const mask = new Uint8Array([1, 0, 1]); | |
| const s = scoreAll(M, q, 2, mask); | |
| eq(s[0], 1); | |
| ok(!Number.isFinite(s[1]) && s[1] < 0, 'masked row should be -Infinity'); | |
| eq(s[2], 1); | |
| }); | |
| t('topK returns K highest scores in descending order', () => { | |
| const scores = new Float32Array([0.1, 0.9, 0.5, 0.7, 0.2]); | |
| const top = topK(scores, 3); | |
| ok(top.length === 3, `expected 3 results, got ${top.length}`); | |
| eq(top[0].score, 0.9); ok(top[0].index === 1); | |
| eq(top[1].score, 0.7); ok(top[1].index === 3); | |
| eq(top[2].score, 0.5); ok(top[2].index === 2); | |
| }); | |
| t('topK skips -Infinity (masked) entries', () => { | |
| const scores = new Float32Array([0.9, -Infinity, 0.5, -Infinity, 0.2]); | |
| const top = topK(scores, 5); | |
| ok(top.length === 3, `expected 3 finite results, got ${top.length}`); | |
| eq(top[0].score, 0.9); eq(top[1].score, 0.5); eq(top[2].score, 0.2); | |
| }); | |
| t('topK with K larger than N returns all sorted', () => { | |
| const scores = new Float32Array([0.3, 0.1, 0.2]); | |
| const top = topK(scores, 10); | |
| ok(top.length === 3); | |
| eq(top[0].score, 0.3); eq(top[1].score, 0.2); eq(top[2].score, 0.1); | |
| }); | |
| t('topK handles ties stably enough (no crash, all returned)', () => { | |
| const scores = new Float32Array([0.5, 0.5, 0.5, 0.5, 0.5]); | |
| const top = topK(scores, 3); | |
| ok(top.length === 3); | |
| for (const r of top) eq(r.score, 0.5); | |
| }); | |
| t('topK with K=0 returns empty', () => { | |
| const top = topK(new Float32Array([1, 2, 3]), 0); | |
| ok(top.length === 0); | |
| }); | |
| // ----- filters.js --------------------------------------------------------- | |
| const ALL = ['lucide', 'phosphor', 'tabler']; | |
| const ids = ['lucide:bell', 'phosphor:bell', 'tabler:bell', 'lucide:user']; | |
| t('buildMask returns null when every library is enabled', () => { | |
| const m = buildMask(ids, new Set(ALL), ALL); | |
| ok(m === null); | |
| }); | |
| t('buildMask returns all-zero when nothing enabled', () => { | |
| const m = buildMask(ids, new Set(), ALL); | |
| ok(m instanceof Uint8Array && m.length === 4); | |
| for (let i = 0; i < 4; i++) ok(m[i] === 0); | |
| }); | |
| t('buildMask keeps exactly the enabled libraries', () => { | |
| const m = buildMask(ids, new Set(['lucide']), ALL); | |
| ok(m[0] === 1); ok(m[1] === 0); ok(m[2] === 0); ok(m[3] === 1); | |
| }); | |
| t('buildMask handles ids with no colon by dropping them', () => { | |
| const m = buildMask(['no-colon', 'lucide:bell'], new Set(['lucide']), ['lucide']); | |
| // 'no-colon' has no slug → drop; 'lucide:bell' → keep. | |
| // (Only one library "enabled" matches all-known, so we should NOT | |
| // get the null short-circuit since the id-without-colon should still | |
| // be dropped.) | |
| ok(m === null || (m[0] === 0 && m[1] === 1), | |
| 'no-colon id should be excluded'); | |
| }); | |
| t('parseId splits slug:name', () => { | |
| const p = parseId('lucide:bell'); | |
| ok(p.slug === 'lucide' && p.name === 'bell'); | |
| }); | |
| t('parseId returns empty slug for no-colon input', () => { | |
| const p = parseId('bare'); | |
| ok(p.slug === '' && p.name === 'bare'); | |
| }); | |
| t('parseId handles names containing colons', () => { | |
| const p = parseId('material-symbols:emoji:smile'); | |
| ok(p.slug === 'material-symbols' && p.name === 'emoji:smile'); | |
| }); | |
| // ----- summary ----------------------------------------------------------- | |
| summary.className = 'summary ' + (fail === 0 ? 'pass' : 'fail'); | |
| summary.textContent = fail === 0 | |
| ? `All ${pass} tests passed.` | |
| : `${fail} failed, ${pass} passed.`; | |
| </script> | |
| </body> | |
| </html> | |