const assert=require('node:assert/strict'); const fs=require('node:fs'); const {launchBrowser}=require('./support/browser.cjs'); const base=process.env.MEGURI_BROWSER_URL||'http://127.0.0.1:3000'; assert(['localhost','127.0.0.1'].includes(new URL(base).hostname)); (async()=>{ const browser=await launchBrowser(); try { const page=await browser.newPage({locale:'en-US',viewport:{width:1280,height:900}}); page.setDefaultTimeout(12000); const errors=[];page.on('pageerror',e=>errors.push(e.message)); await page.goto(base); await page.getByRole('button',{name:'New here? Create an account'}).click(); await page.getByLabel('Username',{exact:true}).fill('map-origin-'+Date.now()); await page.getByLabel('Display name',{exact:true}).fill('Path tester'); await page.getByLabel('Password',{exact:true}).fill('local-browser-test-password'); await page.getByLabel('Email address',{exact:true}).fill('browser-'+Date.now()+'-'+Math.random().toString(16).slice(2)+'@example.com');await page.getByRole('button',{name:'Create account',exact:true}).click(); await page.getByRole('button',{name:'New workspace',exact:true}).click(); const dialog=page.getByRole('dialog'); await dialog.getByLabel('Name',{exact:true}).fill('Layered map regression'); await dialog.getByRole('button',{name:'Save changes',exact:true}).click(); await dialog.waitFor({state:'hidden'}); await page.evaluate(async()=>{ const a=Alpine.$data(document.body),doc=structuredClone(Alpine.raw(a.doc)); const manual={id:crypto.randomUUID(),name:'Manual fallback',type:'rotation',participants:[a.user.id],anchor:a.today(),handover:'11:00:00',shift_days:7};doc.schedules.push(manual); await a.api(a.endpoint('/config'),'PUT',{revision:a.workspace.revision,document:doc});await a.loadWorkspace(); const projects=a.newNode('list');for(const name of ['cse','opd']){const row=a.newNode('dictionary'),owners=a.newNode('list',null,'owners');owners.entries=[a.newNode('person',a.user.id)];row.entries=[a.newNode('text',name,'name'),owners];projects.entries.push(row);} const v={id:crypto.randomUUID(),name:'projects',node:projects},later={id:crypto.randomUUID(),name:'later',node:a.newNode('text','Later schedule')}; a.generationDraft.variables=[later,v]; const loop={id:crypto.randomUUID(),name:'Project',source:{kind:'variable',variable:v.id,path:[]}}; const block=a.newBlock('priority');block.fields.name={kind:'item',iteration:loop.id,path:['name'],prefix:'',suffix:' daytime'};block.fields.participants={kind:'item',iteration:loop.id,path:['owners']};block.fields.fallback=a.literal(manual.id); const policy=a.newBlock('escalation');policy.fields.name={kind:'item',iteration:loop.id,path:['name'],suffix:' escalation'};policy.steps[0].recipient={kind:'output',output:block.id}; const route=a.newBlock('route');route.fields.name={kind:'item',iteration:loop.id,path:['name'],suffix:' route'};route.fields.escalation={kind:'output',output:policy.id}; const second=a.newBlock('rotation');second.fields.name={kind:'variable',variable:later.id,path:[]}; a.generationDraft.rules=[{id:crypto.randomUUID(),name:'Project owners',enabled:true,iterations:[loop],when:null,blocks:[block,policy,route]},{id:crypto.randomUUID(),name:'Later generator',enabled:true,iterations:[],when:null,blocks:[second]}]; await a.openGenerationMap(); }); await page.waitForFunction(()=>!Alpine.$data(document.body).mapBusy); const map=page.locator('dialog.generation-map'),canvas=map.locator('.map-canvas'); assert.deepEqual(await page.evaluate(()=>Alpine.$data(document.body).mapSnapshot.report.issues),[]); const layout=await page.evaluate(()=>Alpine.$data(document.body).mapNodes.map(n=>({id:n.id,lane:n.lane,title:n.title,x:n.position.x,y:n.position.y,manual:n.manual,step:n.step}))); assert(layout.find(n=>n.title==='projects').xn.title==='later').x); assert(Math.max(...layout.filter(n=>n.lane===0).map(n=>n.y))n.lane===1).map(n=>n.y))); assert(Math.max(...layout.filter(n=>n.lane===1).map(n=>n.y))n.lane===2).map(n=>n.y))); assert(layout.some(n=>n.manual&&n.title==='Manual fallback')); const shapes=await canvas.evaluate(el=>{const cy=el._cyreg.cy;return ['variable','rule','output'].map(k=>cy.nodes('[kind="'+k+'"]').first().style('shape'));}); assert.equal(new Set(shapes).size,3); assert.equal(await canvas.evaluate(el=>new Set(['schedule','escalation','route'].map(k=>el._cyreg.cy.nodes('[resourceKind="'+k+'"]').first().style('background-color'))).size),3); const address=map.locator('.map-source-addresses .map-address').filter({has:page.getByRole('button',{name:'name',exact:true})}); await address.getByRole('button',{name:'name',exact:true}).click(); const probe=page.locator('.map-probe');await probe.waitFor(); assert((await probe.innerText()).includes('cse'));assert((await probe.innerText()).includes('opd')); const chain=probe.locator('.map-probe-chain'); assert.equal(await chain.getByRole('button',{name:'View source',exact:true}).count(),0); await chain.getByRole('button',{name:'Project.value',exact:true}).click(); assert((await probe.locator('.map-probe-example').first().innerText()).includes('Path tester')); await chain.getByRole('button',{name:'name',exact:true}).click(); assert(!(await probe.locator('.map-probe-example').first().innerText()).includes('Path tester')); assert.equal(await chain.locator('[aria-current="step"]').innerText(),'name'); assert((await chain.getByRole('button',{name:'name',exact:true}).evaluate(el=>getComputedStyle(el).fontFamily)).includes('monospace')); await probe.getByRole('button',{name:'Done',exact:true}).click(); const resource=map.locator('.map-resource').filter({has:page.locator('.map-resource-name').filter({hasText:'cse daytime'})}); await resource.locator('.map-resource-name button').filter({hasText:'cse'}).click();await probe.waitFor(); assert((await probe.locator('.map-address').textContent()).includes('Project.value.name')); await probe.locator('.map-probe-actions').getByRole('button',{name:'View source',exact:true}).click(); await probe.waitFor({state:'hidden'}); await resource.locator('summary').click(); await resource.getByRole('button',{name:'Path tester',exact:true}).click();await probe.waitFor(); assert((await probe.locator('.map-address').textContent()).includes('Project.value.owners')); await probe.locator('.map-probe-chain').getByRole('button',{name:'var.projects',exact:true}).click(); await probe.getByRole('button',{name:'Back',exact:true}).click(); assert((await probe.locator('.map-address').textContent()).includes('Project.value.owners')); fs.mkdirSync('test-results',{recursive:true});await page.screenshot({path:'test-results/map-provenance-desktop.png'}); await probe.getByRole('button',{name:'Done',exact:true}).click(); const policyResource=map.locator('.map-resource').filter({has:page.locator('.map-resource-name').filter({hasText:'cse escalation'})}); await policyResource.locator('summary').click(); await policyResource.getByRole('button',{name:'cse daytime',exact:true}).click(); await probe.locator('.map-probe-fields').getByRole('button',{name:'Name',exact:true}).click(); assert((await probe.innerText()).includes('cse daytime')); await probe.getByRole('button',{name:'Done',exact:true}).click(); await page.evaluate(()=>{const a=Alpine.$data(document.body);a.mapSelect(a.mapNodes.find(n=>n.manual).id);}); await map.getByText('Manual resources ยท 1',{exact:true}).waitFor(); await page.evaluate(()=>{const a=Alpine.$data(document.body);a.mapSelect(a.mapNodes.find(n=>n.kind==='iteration').id);a.mapToggle();}); assert(await page.evaluate(()=>Alpine.$data(document.body).mapNodes.some(n=>n.id.startsWith('o:')))); assert.deepEqual(await page.evaluate(()=>Alpine.$data(document.body).mapAddressParts('{ var["a.b"].owners[1] }').map(p=>p.text)),['var["a.b"]','.','owners[1]']); await map.getByRole('button',{name:'Fit all',exact:true}).click();await page.screenshot({path:'test-results/generation-map-desktop.png'}); await page.setViewportSize({width:390,height:844});await map.getByRole('button',{name:'Fit all',exact:true}).click();await page.screenshot({path:'test-results/generation-map-mobile.png'}); assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)); await page.setViewportSize({width:1280,height:900});await page.mouse.click(2,2);await map.waitFor({state:'hidden'}); assert.deepEqual(errors,[]); console.log('PASS: layers, first-use ordering, shapes, manual resources, provenance segments, people, history, groups, mobile and backdrop'); }finally{await browser.close();} })().catch(e=>{console.error(e);process.exitCode=1;});