Download test-fixes.js from kavion/quote-api: direct link, hf CLI and curl.
- Browser
- Download file 5.14 kB
-
https://huggingface.co/spaces/kavion/quote-api/resolve/main/test-fixes.js
- Command line
-
hf download hf://spaces/kavion/quote-api/test-fixes.js
-
curl -L -o test-fixes.js https://huggingface.co/spaces/kavion/quote-api/resolve/main/test-fixes.js
5.14 kB
| // Numeric assertions for the group-avatar and media-scale fixes. | |
| // Run: node test-fixes.js → exits non-zero on any failed assertion. | |
| const assert = require('assert') | |
| const { loadImage, createCanvas } = require('canvas') | |
| const generateMethod = require('./methods/generate') | |
| const QuoteGenerate = require('./utils/quote-generate') | |
| // Count opaque pixels in the left avatar column (x < colW) of a PNG buffer. | |
| function leftColumnInk (img, colW) { | |
| const canvas = createCanvas(img.width, img.height) | |
| const ctx = canvas.getContext('2d') | |
| ctx.drawImage(img, 0, 0) | |
| const data = ctx.getImageData(0, 0, colW, img.height).data | |
| let n = 0 | |
| for (let i = 3; i < data.length; i += 4) if (data[i] > 32) n++ | |
| return n | |
| } | |
| async function main () { | |
| const scale = 2 | |
| const msg = (chatId, text) => ({ | |
| chatId, | |
| avatar: true, | |
| from: { id: chatId, name: `User ${chatId}`, photo: {} }, | |
| text | |
| }) | |
| // 1. Same-sender group renders ONE avatar (on the last bubble); the | |
| // two-sender variant renders two. Avatar circles are solid disks of | |
| // d = 50·scale, so the grouped left column must hold roughly half the | |
| // opaque pixels of the ungrouped one (±25% for the tail overlap). | |
| const grouped = await generateMethod({ messages: [msg(7, 'перше'), msg(7, 'друге')], scale }) | |
| const ungrouped = await generateMethod({ messages: [msg(7, 'перше'), msg(8, 'друге')], scale }) | |
| assert.ok(!grouped.error && !ungrouped.error, 'generate failed') | |
| const colW = 50 * scale | |
| const inkGrouped = leftColumnInk(await loadImage(Buffer.from(grouped.image, 'base64')), colW) | |
| const inkUngrouped = leftColumnInk(await loadImage(Buffer.from(ungrouped.image, 'base64')), colW) | |
| const ratio = inkGrouped / inkUngrouped | |
| assert.ok(ratio > 0.35 && ratio < 0.65, | |
| `grouped/ungrouped avatar ink ratio ${ratio.toFixed(2)} — expected ≈ 0.5 (one avatar of two)`) | |
| // 2. Voice renders as an in-bubble attachment row capped at ⅔ of the | |
| // target width. The bubble width is fully deterministic: | |
| // (avatar 50 + gap 10)·s + (rowW + 2·padX 16·s) + shadowPad 12·s, | |
| // where rowW comes from the same drawVoiceRow inputs. | |
| const { drawVoiceRow } = require('./utils/quote-generate/attachments') | |
| const { NAME_COLORS_DARK } = require('./utils/quote-generate/constants') | |
| const waveform = Array.from({ length: 60 }, (_, i) => (i * 7) % 32) | |
| const qg = new QuoteGenerate('0:x') | |
| const voice = await qg.generate('#1b1429', '#1b1429', { | |
| from: { id: 1, name: 'V' }, | |
| voice: { waveform, duration: 42 }, | |
| avatar: false | |
| }, 512, 512, scale, 'apple') | |
| // from.id=1 → NAME_COLORS_DARK[1]; dark background → textColor #fff | |
| const row = drawVoiceRow(waveform, 42, NAME_COLORS_DARK[1], '#fff', scale, 512 * scale * 2 / 3) | |
| assert.ok(row.width <= Math.ceil(512 * scale * 2 / 3), `voice row ${row.width} exceeds ⅔ cap`) | |
| const expectedW = (50 + 10) * scale + (row.width + 2 * 16 * scale) + 12 * scale | |
| assert.ok(Math.abs(voice.width - expectedW) <= 2, | |
| `voice bubble width ${voice.width} != expected ${expectedW}`) | |
| // 3. Image mode: the wallpaper must contrast with the bubble, not blend. | |
| // Sample the wallpaper corner vs the bubble interior; perceived | |
| // brightness (BT.601) must differ noticeably for dark AND light themes. | |
| const brightness = (p) => (p.r * 299 + p.g * 587 + p.b * 114) / 1000 | |
| for (const [bgColor, label] of [['#252e44', 'dark'], ['#e8ecf3', 'light']]) { | |
| const img = await generateMethod({ messages: [msg(7, 'контраст фону')], scale, type: 'image', backgroundColor: bgColor }) | |
| assert.ok(!img.error, 'image generate failed') | |
| const pic = await loadImage(Buffer.from(img.image, 'base64')) | |
| const c = createCanvas(pic.width, pic.height) | |
| const cx = c.getContext('2d') | |
| cx.drawImage(pic, 0, 0) | |
| const get = (x, y) => { | |
| const d = cx.getImageData(x, y, 1, 1).data | |
| return { r: d[0], g: d[1], b: d[2] } | |
| } | |
| // Wallpaper near the corner; bubble interior right of the avatar column | |
| // ((50+10+16)·s + margin, vertical center). | |
| const wallPx = get(8, 8) | |
| const wall = brightness(wallPx) | |
| const bubble = brightness(get(95 * scale / 2 + (50 + 10 + 20) * scale, Math.round(pic.height / 2))) | |
| assert.ok(Math.abs(bubble - wall) >= 18, | |
| `${label}: bubble (${bubble.toFixed(0)}) blends into wallpaper (${wall.toFixed(0)})`) | |
| // Light wallpapers must be pastel, not gray: require visible chroma. | |
| if (label === 'light') { | |
| const chroma = Math.max(wallPx.r, wallPx.g, wallPx.b) - Math.min(wallPx.r, wallPx.g, wallPx.b) | |
| assert.ok(chroma >= 15, `light wallpaper is gray (chroma ${chroma}) — expected a pastel tint`) | |
| } | |
| console.log(` image/${label}: bubble ${bubble.toFixed(0)} vs wallpaper ${wall.toFixed(0)} (Δ${Math.abs(bubble - wall).toFixed(0)})`) | |
| } | |
| console.log('OK: fixes assertions passed') | |
| console.log(` avatar ink ratio grouped/ungrouped = ${ratio.toFixed(3)} (≈0.5)`) | |
| console.log(` voice bubble width = ${voice.width} (expected ${expectedW}, row ${row.width})`) | |
| } | |
| main().catch((err) => { | |
| console.error('FAIL:', err.message) | |
| process.exit(1) | |
| }) | |