/* Verify every theme switch updates group card colors in the UI (no reload). */ const { spawn } = require("node:child_process"); const fs = require("node:fs"); const http = require("node:http"); const EDGE = "C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"; const PORT = 9357; function httpJson(p){return new Promise((res,rej)=>{const r=http.request({host:"127.0.0.1",port:PORT,path:p},(x)=>{let d="";x.on("data",c=>d+=c);x.on("end",()=>{try{res(JSON.parse(d))}catch{res(d)}})});r.on("error",rej);r.end()})} (async()=>{ const prof=fs.mkdtempSync(require("node:os").tmpdir()+"/edge-prof-"); const proc=spawn(EDGE,["--headless=new","--remote-debugging-port="+PORT,"--user-data-dir="+prof,"--no-first-run","--no-default-browser-check","--disable-gpu","about:blank"],{stdio:"ignore"}); let tg=null;for(let i=0;i<40;i++){await new Promise(r=>setTimeout(r,500));try{tg=await httpJson("/json/list");break}catch{}} const page=tg.find(t=>t.type==="page"); const ws=new WebSocket(page.webSocketDebuggerUrl); await new Promise(r=>ws.onopen=r);let id=0; const send=(m,p={})=>new Promise(res=>{const i2=++id;ws.send(JSON.stringify({id:i2,method:m,params:p}));const t=setInterval(()=>{const f=ws._buf.find(b=>b.id===i2);if(f){clearInterval(t);res(f.result??f.error)}},10)}); ws._buf=[];const errs=[]; ws.onmessage=(m)=>{const j=JSON.parse(m.data);if(j.id){ws._buf.push(j);return;} if(j.method==="Runtime.consoleAPICalled"&&j.params.type==="error"){errs.push(j.params.args.map(a=>(a.value??a.description??"")).join(" ").slice(0,150));} if(j.method==="Runtime.exceptionThrown"){errs.push("EXC: "+(j.params.exceptionDetails?.exception?.description||"").slice(0,120));}}; await send("Runtime.enable");await send("Page.enable");await send("Network.enable"); const cv=fs.readFileSync(process.argv[1],"utf8").split("\n").map(l=>l.trim()).find(l=>l.includes("authjs.session-token")).split("\t").pop(); await send("Network.setCookies",{cookies:[{name:"authjs.session-token",value:cv,domain:"localhost",path:"/",httpOnly:true,sameSite:"Lax"}]}); await send("Emulation.setDeviceMetricsOverride",{width:1440,height:900,deviceScaleFactor:1}); await send("Page.navigate",{url:"http://localhost:3210/"}); await new Promise(r=>setTimeout(r,5000)); // wait until group cards (divs with inline borderColor+backgroundColor) have rendered const cardQ=`Array.from(document.querySelectorAll("div")).some(d=>{const st=d.getAttribute("style")||"";return /background-color/.test(st)&&/border-color/.test(st)})`; for(let i=0;i<30;i++){ if(await send("Runtime.evaluate",{expression:cardQ,returnByValue:true}).then(r=>r.result?.value)){ break; } await new Promise(r=>setTimeout(r,1000)); } await new Promise(r=>setTimeout(r,500)); const ev=(expr)=>send("Runtime.evaluate",{expression:expr,returnByValue:true,awaitPromise:true}).then(r=>r.result?.value ?? r.exceptionDetails?.exception?.description); const click=async(x,y)=>{await send("Input.dispatchMouseEvent",{type:"mousePressed",x,y,button:"left",clickCount:1});await send("Input.dispatchMouseEvent",{type:"mouseReleased",x,y,button:"left",clickCount:1});}; // Option button scoped to the open dropdown (avoids the "Default" *project* nav item) const optionAt=async(name)=>{const v=await ev(`(()=>{ const menu=document.querySelector('[data-slot="dropdown-menu-content"]'); if(!menu) return null; const b=Array.from(menu.querySelectorAll("button")).find(x=>(x.textContent||"").trim()==="${name}"); if(!b) return null; const r=b.getBoundingClientRect(); return JSON.stringify({x:Math.round(r.x+12),y:Math.round(r.y+r.height/2)}); })()`); return v?JSON.parse(v):null;}; const openMenu=async()=>{const v=await ev(`(()=>{const b=Array.from(document.querySelectorAll("button")).find(x=>(x.textContent||"").trim()==="Theme");if(!b)return null;const r=b.getBoundingClientRect();return JSON.stringify({x:Math.round(r.x+r.width/2),y:Math.round(r.y+r.height/2)})})()`);if(!v)return false;const p=JSON.parse(v);await click(p.x,p.y);await new Promise(r=>setTimeout(r,500));return true;}; const state=async(label)=>{const out=JSON.stringify(await ev(`(()=>{ const cards=Array.from(document.querySelectorAll("[class*=rounded-2xl]")).filter(c=>/background-color/.test(c.getAttribute("style")||"")&&/border-color/.test(c.getAttribute("style")||"")); const card=cards[0]||{getAttribute:()=>""}; const s=card.getAttribute("style")||""; return { html:document.documentElement.className.split(" ").filter(Boolean).slice(-1).join(","), cards:cards.length, cardBg:(s.match(/background-color:([^;]+)/)||[])[1], cardBorder:(s.match(/border-color:([^;]+)/)||[])[1], bodyBg:getComputedStyle(document.body).backgroundColor }; })()`));console.log(label,out);return out;}; const pickCard=await ev(`Array.from(document.querySelectorAll("div")).filter(d=>{const st=d.getAttribute("style")||"";return /background-color/.test(st)&&/border-color/.test(st)}).length`); console.log(" cards-with-style:", pickCard); let s1 = await state("1) initial (dark) "); await openMenu(); let o = await optionAt("Default"); console.log(" -> clicking Default at", JSON.stringify(o)); if(o){await click(o.x,o.y);} await new Promise(r=>setTimeout(r,900)); let s2 = await state("2) after Default "); await openMenu(); o = await optionAt("Sunset"); console.log(" -> clicking Sunset at", JSON.stringify(o)); if(o){await click(o.x,o.y);} await new Promise(r=>setTimeout(r,900)); let s3 = await state("3) after Sunset "); await openMenu(); o = await optionAt("Ocean"); console.log(" -> clicking Ocean at", JSON.stringify(o)); if(o){await click(o.x,o.y);} await new Promise(r=>setTimeout(r,900)); let s4 = await state("4) after Ocean "); await openMenu(); o = await optionAt("Dark"); console.log(" -> clicking Dark at", JSON.stringify(o)); if(o){await click(o.x,o.y);} await new Promise(r=>setTimeout(r,900)); let s5 = await state("5) back to Dark "); const parse=(s)=>JSON.parse(s.slice(s.indexOf("{"))); const ok1 = parse(s2).cardBorder !== parse(s1).cardBorder || parse(s2).html !== parse(s1).html; const ok2 = parse(s3).cardBg !== parse(s2).cardBg; const ok3 = parse(s4).cardBg !== parse(s3).cardBg; const ok4 = parse(s5).cardBg !== parse(s4).cardBg; console.log("TRANSITIONS OK:", ok1, ok2, ok3, ok4, "| ALL:", ok1&&ok2&&ok3&&ok4); const real=errs.filter(e=>!/favicon|manifest|icon|404/i.test(e)); console.log("CONSOLE ERRORS:", real.length); real.slice(0,5).forEach(e=>console.log(" -",e)); proc.kill();process.exit(0); })().catch(e=>{console.error("FAIL",e.message);process.exit(1)});