Files
juzi/h5/test/contour-sides.mjs

92 lines
6.8 KiB
JavaScript

// Browser-only fixtures, no writes/paid detection. Full photo opens in the
// existing zoom overlay while thumbnail cropping is CSS-only.
import assert from 'node:assert/strict';
import fs from 'node:fs';
import {execSync} from 'node:child_process';
import {pathToFileURL} from 'node:url';
const {chromium}=await import(pathToFileURL(execSync('npm root -g',{encoding:'utf8'}).trim()+'/playwright/index.mjs').href);
const browser=await chromium.launch();
try {
const page=await browser.newPage({viewport:{width:375,height:1000},isMobile:true,hasTouch:true});
const errors=[];page.on('pageerror',e=>errors.push(e.message));
const source=fs.readFileSync(new URL('../js/app.js',import.meta.url),'utf8');
await page.route('**/api/**',r=>r.fulfill({json:{ok:true,user:{kind:'browser'}}}));
await page.route('**/js/app.js*',r=>r.fulfill({contentType:'text/javascript',body:source.replace(/\}\)\(\);\s*$/,`
window.__sideTest=function(ph){
var fx=window.MOCK_FIXTURE,vm=SkinScore.buildViewModel(fx.result,fx.side,{});
renderReport(vm,fx.photos[0],null,{result:fx.result},ph);showScreen('s-report');
};})();`)}));
await page.goto((process.env.H5_BASE_URL||'http://127.0.0.1:8080/')+'?mock=1&screen=report');
const photo=color=>'data:image/svg+xml,'+encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="320" height="240"><rect width="320" height="240" fill="'+color+'"/></svg>');
const left=photo('red'),right=photo('blue');
for(const width of [320,375,414,480]) {
await page.setViewportSize({width,height:1000});
await page.evaluate(ph=>window.__sideTest(ph),[null,left,right]);
await page.waitForFunction(()=>[...document.querySelectorAll('#contour-sides figure')].every(f=>!f.hidden&&f.querySelector('img').naturalWidth===320));
const figures=page.locator('#contour-sides figure');
assert.equal(await figures.count(),2);
assert.equal((await page.locator('#contour-sides').textContent()).trim(),'','no visible left/right captions');
assert.equal(await page.locator('#contour-left').getAttribute('src'),left);
assert.equal(await page.locator('#contour-right').getAttribute('src'),right);
await page.waitForFunction(()=>document.querySelector('#ditem-img1').naturalWidth>0);
const crop=await page.locator('#ditem-img1').evaluate(img=>{
const i=img.getBoundingClientRect(),f=img.parentElement.getBoundingClientRect();
const other=document.querySelector('#ditem-img2').parentElement.getBoundingClientRect();
return{ratio:f.height/i.height,top:i.top-f.top,width:i.width-f.width,other:other.height/i.height,overflow:getComputedStyle(img.parentElement).overflow};
});
assert(Math.abs(crop.ratio-.9)<.002);assert(Math.abs(crop.top)<1);assert(Math.abs(crop.width)<1);
assert(Math.abs(crop.other-.9)<.002,'wrinkle front frame matches contour crop');assert.equal(crop.overflow,'hidden');
const offset=await page.locator('#ditem-img1').getAttribute('data-content-offset');
assert(offset!=null&&Number(offset)>=0&&Number(offset)<=.090001);
const fullFront=await page.locator('#ditem-img1').getAttribute('src');
await page.locator('#ditem-img1').click();
assert.notEqual(await page.locator('#zoom-img').getAttribute('src'),fullFront,'front zoom follows clipped thumbnail');
await page.waitForFunction(()=>document.querySelector('#zoom-img').complete);
assert(Math.abs(await page.locator('#zoom-img').evaluate(e=>e.naturalWidth/e.naturalHeight)-215/250.2)<.003);
await page.locator('#p-zoom').click();
const geo=await page.locator('.contour-side-photo').first().evaluate(e=>{const r=e.getBoundingClientRect();return{w:r.width,h:r.height,fit:getComputedStyle(e.querySelector('img')).objectFit};});
assert(Math.abs(geo.h/geo.w-92.25/104.35)<.002);assert.equal(geo.fit,'cover');
for(const img of await page.locator('.contour-side-photo img').all()) {
const fill=await img.evaluate(e=>{
const m=new DOMMatrix(getComputedStyle(e).transform),r=e.getBoundingClientRect(),b=e.parentElement.getBoundingClientRect();
return{sx:m.a,sy:m.d,shift:m.f/b.height,top:r.top-b.top,bottom:r.bottom-b.bottom};
});
assert(Math.abs(fill.sx-2.8)<.001);assert(Math.abs(fill.sy-2.8)<.001);assert(Math.abs(fill.shift-(-.25))<.001);
assert(fill.top<=.01&&fill.bottom>0,'zoomed fill covers frame without blank top edge');
}
const spacing=await page.locator('#contour-sides').evaluate(e=>{
const frame=document.querySelector('.contour-front-frame'),f=frame.getBoundingClientRect(),s=e.getBoundingClientRect();
return{u:f.width/215,horizontal:parseFloat(getComputedStyle(e).columnGap),vertical:s.top-f.bottom};
});
assert(Math.abs(spacing.horizontal/spacing.u-6.3)<.1);
assert(Math.abs(spacing.vertical/spacing.u-7.56)<.1);
await page.getByRole('button',{name:'查看左脸完整大图'}).click();
assert(await page.locator('#p-zoom').evaluate(e=>e.classList.contains('show')));
assert.notEqual(await page.locator('#zoom-img').getAttribute('src'),left);
await page.waitForFunction(()=>document.querySelector('#zoom-img').complete);
assert(Math.abs(await page.locator('#zoom-img').evaluate(e=>e.naturalWidth/e.naturalHeight)-geo.w/geo.h)<.003);
await page.locator('#p-zoom').click();
await page.getByRole('button',{name:'查看右脸完整大图'}).click();
assert.notEqual(await page.locator('#zoom-img').getAttribute('src'),right);
await page.keyboard.press('Escape');
assert(!await page.locator('#p-zoom').evaluate(e=>e.classList.contains('show')));
await page.locator('.card-detail').first().locator('[data-tab="1"]').click();
await page.waitForTimeout(400);
assert(await page.locator('#contour-sides').evaluate(e=>{
const r=e.getBoundingClientRect(),v=e.closest('.detail-viewport').getBoundingClientRect();
return e.closest('.dpanel').getAttribute('aria-hidden')==='true'&&r.right<=v.left+1;
}),'contour thumbnails remain clipped outside the wrinkle tab');
}
await page.evaluate(ph=>window.__sideTest(ph),[null,left,null]);
await page.waitForFunction(()=>!document.querySelector('#contour-left').closest('figure').hidden);
assert(await page.locator('#contour-right').evaluate(e=>e.closest('figure').hidden&&!e.hasAttribute('src')));
await page.evaluate(()=>window.__sideTest([null,null,null]));
assert(await page.locator('#contour-sides').evaluate(e=>e.hidden));
assert.equal(await page.locator('#contour-sides img[src]').count(),0,'new report clears previous side photos');
await page.evaluate(()=>window.__sideTest([null,'data:image/jpeg;base64,broken',null]));
await page.waitForFunction(()=>!document.querySelector('#contour-left').hasAttribute('src'));
assert(await page.locator('#contour-sides').evaluate(e=>e.hidden));
assert.deepEqual(errors,[]);
console.log('PASS contour side photos: 4 widths, preserved thumbnail heights and 30% tighter gaps, left/right sources, full-aspect zoom, close/Escape, tab isolation, one/missing/broken photos and stale-photo cleanup');
} finally {await browser.close();}