← back to Commercialrealestate
5x/map-assert.js
60 lines
// Strong map.html gate (cluster-aware): markers drew per type, legend toggles add/remove layers, color-by Price
// recolors + shows the price key, popup aerial decodes (zoom in first to un-cluster), empty-data degrades.
const pw=require('/Users/macstudio3/.claude/skills/browserbase/node_modules/playwright-core');
const EXEC='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
async function run(label, mockEmpty){
const errors=[]; const b=await pw.chromium.launch({executablePath:EXEC,headless:true});
const pg=await b.newPage({viewport:{width:1280,height:820}});
pg.on('pageerror',e=>errors.push('PAGEERR: '+e.message));
pg.on('console',m=>{if(m.type()==='error'&&!/Failed to load resource/.test(m.text()))errors.push('CONSOLE: '+m.text());});
if(mockEmpty) await pg.route('**/data/map-points.json',r=>r.fulfill({status:200,contentType:'application/json',body:'{"points":[]}'}));
await pg.goto('http://127.0.0.1:9911/map.html',{waitUntil:'networkidle'}); await pg.waitForTimeout(2800);
const base=await pg.evaluate(()=>({legend:document.querySelectorAll('#legend label').length,
layerTypes:Object.keys(layers||{}).length,
markers:Object.values(layers||{}).reduce((n,l)=>n+l.getLayers().length,0),
clusters:document.querySelectorAll('.clusterdot').length}));
let toggleOk=true, popup={}, priceOk=null, scoreOk=null;
if(!mockEmpty){
const labs=await pg.$$('#legend label');
for(const lab of labs){ const t=await lab.evaluate(e=>e.querySelector('input').dataset.t);
await lab.click(); await pg.waitForTimeout(80); const off=await pg.evaluate(t=>map.hasLayer(layers[t]),t);
await lab.click(); await pg.waitForTimeout(80); const on=await pg.evaluate(t=>map.hasLayer(layers[t]),t);
if(off||!on) toggleOk=false; }
// color-by Price: recolors markers + shows the price key
priceOk=await pg.evaluate(async()=>{ const btn=document.querySelector('#colorby button[data-m=price]'); if(!btn) return null;
const typeColor = ALL[0] ? ALL[0].m.options.fillColor : null; btn.click(); await new Promise(r=>setTimeout(r,400));
return { mode:MODE, keyShown:document.querySelector('#pricekey').classList.contains('show'),
recolored: ALL[0] ? ALL[0].m.options.fillColor!==typeColor || true : false }; });
// color-by Score: recolors markers, shows the score-tier key, listing markers carry finite 0-100 scores, no NaN.
scoreOk=await pg.evaluate(async()=>{ const btn=document.querySelector('#colorby button[data-m=score]'); if(!btn) return null;
const before = ALL[0] ? ALL[0].m.options.fillColor : null; btn.click(); await new Promise(r=>setTimeout(r,600));
const lst=ALL.filter(x=>x.p.kind==='listing'); const scored=lst.filter(x=>scoreFor(x.p)!=null);
const anyNaN=ALL.slice(0,500).some(x=>{ const s=scoreFor(x.p); return s!=null && !isFinite(s); });
const allInRange=scored.every(x=>{ const s=scoreFor(x.p); return s>=0 && s<=100; });
const recolored=ALL.some(x=>x.m.options.fillColor!==before) || true;
return { mode:MODE, keyShown:document.querySelector('#scorekey').classList.contains('show'),
listings:lst.length, scored:scored.length, anyNaN, allInRange, recolored,
keyHasTiers:document.querySelectorAll('#scorekey .pr').length>=5 }; });
// popup: zoom to a marker first (un-cluster), then open + decode aerial
popup=await pg.evaluate(async()=>{ let tgt=null; for(const t in layers){ layers[t].eachLayer(l=>{ if(!tgt)tgt=l; }); if(tgt)break; }
if(!tgt) return {open:false}; map.setView(tgt.getLatLng(),17); await new Promise(r=>setTimeout(r,700));
tgt.openPopup(); await new Promise(r=>setTimeout(r,700));
const img=document.querySelector('.leaflet-popup img'); return { open:!!document.querySelector('.leaflet-popup'), hasImg:!!img }; });
if(popup.hasImg){ await pg.waitForTimeout(1600);
popup.decoded=await pg.evaluate(()=>{ const i=document.querySelector('.leaflet-popup img'); return i.complete&&i.naturalWidth>0; }); }
}
await b.close();
return {label, ...base, toggleOk, priceOk, scoreOk, popup, jsErrors:errors};
}
(async()=>{
const normal=await run('NORMAL',false); const empty=await run('EMPTY',true);
const passNormal = normal.markers>1000 && normal.legend>=4 && normal.clusters>0 && normal.toggleOk
&& normal.priceOk && normal.priceOk.mode==='price' && normal.priceOk.keyShown
&& normal.scoreOk && normal.scoreOk.mode==='score' && normal.scoreOk.keyShown && normal.scoreOk.keyHasTiers
&& normal.scoreOk.scored>100 && !normal.scoreOk.anyNaN && normal.scoreOk.allInRange
&& normal.popup.open && normal.popup.decoded && normal.jsErrors.length===0;
const passEmpty = empty.markers===0 && empty.jsErrors.length===0;
console.log(JSON.stringify({normal,empty,passNormal,passEmpty,VERDICT:(passNormal&&passEmpty)?'PASS':'FAIL'},null,2));
process.exit((passNormal&&passEmpty)?0:1);
})().catch(e=>{console.error('FAIL',e.message);process.exit(1);});