Spaces:
Running
Running
Download scripts/test-viewer-ui.mjs from bep40/VSEARCH: direct link, hf CLI and curl.
- Browser
- Download file 7.79 kB
-
https://huggingface.co/spaces/bep40/VSEARCH/resolve/main/scripts/test-viewer-ui.mjs
- Command line
-
hf download hf://spaces/bep40/VSEARCH/scripts/test-viewer-ui.mjs
-
curl -L -o test-viewer-ui.mjs https://huggingface.co/spaces/bep40/VSEARCH/resolve/main/scripts/test-viewer-ui.mjs
7.79 kB
| /** | |
| * Browser checks for the restored viewer CSS, the ten-ring limit, and pill | |
| * clearance. | |
| * | |
| * The CSS regression this file exists for was silent: opening a photograph | |
| * produced an unstyled <dialog> that still worked, still showed the image, and | |
| * just looked nothing like the design. Nothing throws, so only a computed-style | |
| * assertion catches it. | |
| */ | |
| import { chromium } from 'playwright'; | |
| const BASE = process.env.BASE_URL || 'http://127.0.0.1:4173/'; | |
| let passed = 0, failed = 0; | |
| const test = async (name, fn) => { | |
| try { await fn(); console.log(' ok ' + name); passed++; } | |
| catch (error) { console.log(' FAIL ' + name + '\n ' + error.message.split('\n')[0]); failed++; } | |
| }; | |
| const assert = (cond, message) => { if (!cond) throw new Error(message); }; | |
| const browser = await chromium.launch(); | |
| const page = await browser.newPage({ viewport: { width: 1600, height: 950 } }); | |
| const errors = []; | |
| page.on('pageerror', error => errors.push(error.message)); | |
| await page.goto(BASE, { waitUntil: 'domcontentloaded' }); | |
| await page.waitForSelector('.image-tile', { timeout: 120000 }); | |
| await page.waitForTimeout(6000); | |
| console.log('\nrestored viewer CSS'); | |
| // ────────────────────────────────────────────────────────────────────────── | |
| await test('the viewer opens full-screen with its own background', async () => { | |
| // force: true because the field is deliberately alive -- every tile drifts a | |
| // few px per frame, so a click aimed at a position sampled a moment ago can | |
| // land beside the tile. That is the field working, not a bug, and it is why | |
| // the shot here is dispatched at the element rather than at coordinates. | |
| await page.locator('.image-tile').first().click({ force: true, timeout: 15000 }); | |
| await page.waitForSelector('.photo-viewer[open]', { timeout: 30000 }); | |
| const box = await page.locator('.photo-viewer').boundingBox(); | |
| const style = await page.evaluate(() => { | |
| const v = document.querySelector('.photo-viewer'); | |
| const s = getComputedStyle(v); | |
| return { position: s.position, bg: s.backgroundColor, w: s.width, h: s.height }; | |
| }); | |
| assert(style.position === 'fixed', 'viewer is ' + style.position + ', not fixed'); | |
| assert(box.width >= 1500 && box.height >= 900, 'viewer is ' + Math.round(box.width) + 'x' + Math.round(box.height)); | |
| assert(style.bg !== 'rgba(0, 0, 0, 0)', 'viewer has no background'); | |
| }); | |
| await test('the toolbar is a bar, not stacked plain text', async () => { | |
| // The regression rendered this as inline-flow divs at full width. A real | |
| // toolbar is absolutely positioned over the image, which is what stops the | |
| // share button sitting on top of the photograph. | |
| const style = await page.evaluate(() => { | |
| const t = document.querySelector('.photo-toolbar'); | |
| return t ? { position: getComputedStyle(t).position, display: getComputedStyle(t).display } : null; | |
| }); | |
| assert(style, 'no .photo-toolbar found'); | |
| assert(style.position === 'absolute', 'toolbar is ' + style.position); | |
| }); | |
| await test('the close button is sized and round, not a bare glyph', async () => { | |
| const box = await page.locator('.photo-toolbar button').first().boundingBox(); | |
| assert(box && box.width >= 28 && box.height >= 28, | |
| 'close button is ' + (box ? Math.round(box.width) + 'x' + Math.round(box.height) : 'missing')); | |
| }); | |
| await test('the frame sits inside the viewport, not flush to an edge', async () => { | |
| const box = await page.locator('.photo-frame').boundingBox(); | |
| assert(box && box.width > 100 && box.height > 100, 'frame is ' + JSON.stringify(box)); | |
| assert(box.x >= 0 && box.y >= 0 && box.x + box.width <= 1601 && box.y + box.height <= 951, | |
| 'frame escapes the viewport'); | |
| }); | |
| await test('the detail panel is styled when the photograph has metadata', async () => { | |
| const n = await page.locator('.photo-detail').count(); | |
| if (!n) { console.log(' (skipped: this photograph has no metadata panel)'); return; } | |
| const style = await page.evaluate(() => { | |
| const d = document.querySelector('.photo-detail'); | |
| const s = getComputedStyle(d); | |
| return { position: s.position, z: s.zIndex }; | |
| }); | |
| assert(['absolute', 'fixed'].includes(style.position), 'panel is ' + style.position); | |
| }); | |
| console.log('\nten rings, not sixty'); | |
| // ────────────────────────────────────────────────────────────────────────── | |
| await test('at most ten tiles carry the neon', async () => { | |
| await page.keyboard.press('Escape'); | |
| await page.waitForTimeout(1500); | |
| const ringed = await page.locator('.image-tile.featured').count(); | |
| assert(ringed <= 10, ringed + ' tiles are ringed, expected at most 10'); | |
| }); | |
| await test('more photographs are shown than before', async () => { | |
| const total = await page.locator('.image-tile').count(); | |
| assert(total > 96, 'only ' + total + ' tiles; the field should now hold 150 on desktop'); | |
| }); | |
| await test('un-ringed new photographs are still visible', async () => { | |
| // "còn lại hiển thị bình thường" -- the rest must render as ordinary tiles. | |
| const plain = await page.evaluate(() => { | |
| const tiles = [...document.querySelectorAll('.image-tile')]; | |
| return tiles.filter(t => !t.classList.contains('featured')).length; | |
| }); | |
| assert(plain > 10, 'no un-ringed tiles found'); | |
| }); | |
| console.log('\npill clearance'); | |
| // ────────────────────────────────────────────────────────────────────────── | |
| await test('no ringed tile overlaps the search pill', async () => { | |
| // The brief is explicit: big, near the search box, but NOT covering it. | |
| // Measured against every ringed tile's real box, not its centre point. | |
| const worst = await page.evaluate(() => { | |
| const pill = document.querySelector('.search-pill').getBoundingClientRect(); | |
| let overlap = 0, who = null; | |
| for (const tile of document.querySelectorAll('.image-tile.featured')) { | |
| const r = tile.getBoundingClientRect(); | |
| const w = Math.max(0, Math.min(r.right, pill.right) - Math.max(r.left, pill.left)); | |
| const h = Math.max(0, Math.min(r.bottom, pill.bottom) - Math.max(r.top, pill.top)); | |
| const area = w * h; | |
| if (area > overlap) { overlap = area; who = tile.dataset.imageId; } | |
| } | |
| return { overlap, who, pill: pill.width * pill.height }; | |
| }); | |
| assert(worst.overlap === 0, | |
| `tile ${worst.who} covers ${Math.round(worst.overlap)}px² of the pill (${Math.round(worst.pill)}px² total)`); | |
| }); | |
| await test('a ringed tile is still larger than an ordinary one', async () => { | |
| const r = await page.evaluate(() => { | |
| const area = el => { const b = el.getBoundingClientRect(); return b.width * b.height; }; | |
| const f = document.querySelector('.image-tile.featured'); | |
| const o = [...document.querySelectorAll('.image-tile')].find(t => !t.classList.contains('featured')); | |
| return { f: area(f), o: area(o) }; | |
| }); | |
| assert(r.f > r.o * 1.15, `ringed ${Math.round(r.f)}px² vs ordinary ${Math.round(r.o)}px²`); | |
| }); | |
| console.log('\nno regressions'); | |
| // ────────────────────────────────────────────────────────────────────────── | |
| await test('no uncaught page errors', async () => { | |
| assert(errors.length === 0, errors.slice(0, 3).join(' | ')); | |
| }); | |
| await browser.close(); | |
| console.log(`\n${passed} passed, ${failed} failed`); | |
| process.exit(failed ? 1 : 0); |