74 lines
7.8 KiB
JavaScript
74 lines
7.8 KiB
JavaScript
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('config-transfer-'+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('Portable config regression');
|
||
|
|
await dialog.getByRole('button',{name:'Save changes',exact:true}).click();
|
||
|
|
await dialog.waitFor({state:'hidden'});
|
||
|
|
|
||
|
|
await page.evaluate(()=>{const a=Alpine.$data(document.body);a.view='generation';});
|
||
|
|
const initial=await page.evaluate(()=>({revision:Alpine.$data(document.body).workspace.revision,workspace:Alpine.$data(document.body).workspace.id}));
|
||
|
|
await page.getByRole('button',{name:'Import / Export',exact:true}).click();
|
||
|
|
const modal=page.getByRole('dialog');
|
||
|
|
await modal.getByLabel('Configuration · meguri/v2 JSON').waitFor();
|
||
|
|
await page.waitForFunction(()=>!!Alpine.$data(document.body).transferExport);
|
||
|
|
await modal.getByRole('button',{name:'Guide',exact:true}).click();
|
||
|
|
const guide=modal.locator('.config-guide');await guide.waitFor({state:'visible'});
|
||
|
|
await page.context().grantPermissions(['clipboard-read','clipboard-write'],{origin:base});
|
||
|
|
await modal.getByRole('button',{name:'Copy full guide',exact:true}).click();
|
||
|
|
const copiedGuide=await page.evaluate(()=>navigator.clipboard.readText());
|
||
|
|
assert(copiedGuide.includes('$lookup'));assert(copiedGuide.includes('not_equals'));assert(copiedGuide.includes('ends-with'));
|
||
|
|
const sample=JSON.parse(copiedGuide.match(/```json\n([\s\S]*?)\n```/)[1]);
|
||
|
|
const guidePreview=await page.evaluate(async text=>{const a=Alpine.$data(document.body);return a.api(a.endpoint('/generation-import-preview'),'POST',{...a.transferBase,text});},JSON.stringify(sample));
|
||
|
|
assert.deepEqual(guidePreview.preview.issues,[]);assert.equal(guidePreview.preview.changes.filter(c=>c.action==='create').length,4);
|
||
|
|
fs.mkdirSync('test-results',{recursive:true});await page.screenshot({path:'test-results/config-guide-desktop.png'});
|
||
|
|
await page.setViewportSize({width:390,height:844});await page.screenshot({path:'test-results/config-guide-mobile.png'});assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));await page.setViewportSize({width:1280,height:900});
|
||
|
|
await modal.getByRole('button',{name:'Import',exact:true}).click();
|
||
|
|
const input=modal.getByLabel('Paste or edit configuration'),apply=modal.getByRole('button',{name:'Apply to shared draft',exact:true});
|
||
|
|
await input.fill('{ broken');
|
||
|
|
await modal.getByRole('button',{name:'Guide',exact:true}).click();await modal.getByRole('button',{name:'Import',exact:true}).click();assert.equal(await input.inputValue(),'{ broken');
|
||
|
|
await modal.getByRole('alert').filter({hasText:'Invalid configuration'}).waitFor();assert(await apply.isDisabled());
|
||
|
|
const config={format:'meguri/v1',variables:[{name:'projects',node:{kind:'list',entries:['cse','opd'].map(name=>({id:name,kind:'dictionary',entries:[{kind:'text',key:'name',value:name},{kind:'list',key:'owners',entries:[{kind:'person',value:'owner@example.com'}]}]}))}},{name:'keep me',node:{kind:'text',value:'retained'}}],generators:[{name:'Weekly projects',iterations:[{name:'Project',source:{kind:'variable',variable:'projects'}}],blocks:[{id:'weekly',kind:'rotation',fields:{name:{kind:'item',iteration:'Project',path:['name'],suffix:' on-call'},participants:{kind:'item',iteration:'Project',path:['owners']},anchor:{kind:'literal',value:'2026-09-28'},shift_days:{kind:'literal',value:7},handover:{kind:'literal',value:'11:00'}}}]}]};
|
||
|
|
await input.fill(JSON.stringify(config,null,2));
|
||
|
|
await page.waitForFunction(()=>Alpine.$data(document.body).transferCanApply());
|
||
|
|
assert((await modal.locator('.transfer-preview').innerText()).includes('cse on-call'));
|
||
|
|
assert.deepEqual(await page.evaluate(()=>Alpine.$data(document.body).transferResult.contacts.map(p=>p.email)),['owner@example.com']);
|
||
|
|
assert.equal(await page.evaluate(()=>Alpine.$data(document.body).workspace.revision),initial.revision);
|
||
|
|
const before=await page.evaluate(async()=>{const a=Alpine.$data(document.body);return (await a.api(a.endpoint())).generation_draft;});assert(!before.payload||!before.payload.rules.length);
|
||
|
|
fs.mkdirSync('test-results',{recursive:true});await page.screenshot({path:'test-results/config-import-desktop.png'});
|
||
|
|
await page.setViewportSize({width:390,height:844});await page.screenshot({path:'test-results/config-import-mobile.png'});assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));await page.setViewportSize({width:1280,height:900});
|
||
|
|
await apply.click();await modal.waitFor({state:'hidden'});
|
||
|
|
await page.waitForFunction(()=>!Alpine.$data(document.body).generationLoading);
|
||
|
|
assert.equal(await page.evaluate(()=>Alpine.$data(document.body).workspace.revision),initial.revision);
|
||
|
|
assert.equal(await page.evaluate(()=>Alpine.$data(document.body).doc.schedules.length),0);
|
||
|
|
const ids=await page.evaluate(()=>Alpine.$data(document.body).generationDraft.rules.map(r=>r.blocks.map(b=>b.id)));
|
||
|
|
await page.getByRole('button',{name:'Import / Export',exact:true}).click();await page.waitForFunction(()=>!!Alpine.$data(document.body).transferExport);
|
||
|
|
const exported=await modal.getByLabel('Configuration · meguri/v2 JSON').inputValue();assert(exported.includes('owner@example.com'));assert.equal(JSON.parse(exported).format,'meguri/v2');assert(!exported.includes(ids[0][0]));await page.screenshot({path:'test-results/config-v2-export.png'});
|
||
|
|
const downloaded=page.waitForEvent('download');await modal.getByRole('button',{name:'Download JSON',exact:true}).click();assert.equal((await downloaded).suggestedFilename(),'meguri-config.json');
|
||
|
|
const edited=JSON.parse(exported);delete edited.variables['keep me'];edited.generators[0].outputs[0].shift_days=14;
|
||
|
|
await modal.getByRole('button',{name:'Import',exact:true}).click();await input.fill(JSON.stringify(edited));await page.waitForFunction(()=>Alpine.$data(document.body).transferCanApply());
|
||
|
|
assert.equal(await page.evaluate(()=>Alpine.$data(document.body).transferResult.preview.changes.filter(c=>c.action==='create').length),2);
|
||
|
|
await apply.click();await modal.waitFor({state:'hidden'});
|
||
|
|
assert.deepEqual(await page.evaluate(()=>Alpine.$data(document.body).generationDraft.rules.map(r=>r.blocks.map(b=>b.id))),ids);
|
||
|
|
assert(await page.evaluate(()=>Alpine.$data(document.body).generationDraft.variables.some(v=>v.name==='keep me')));
|
||
|
|
await page.reload();await page.waitForFunction(()=>Alpine.$data(document.body).generationDraft?.rules.length===1);
|
||
|
|
assert.equal(await page.evaluate(()=>Alpine.$data(document.body).generationDraft.rules[0].blocks[0].fields.shift_days.value),14);
|
||
|
|
assert.deepEqual(errors,[]);
|
||
|
|
console.log('PASS: portable config live preview, parse errors, no writes before apply, shared draft persistence, identities, merge, export, download and mobile');
|
||
|
|
}finally{await browser.close();}
|
||
|
|
})().catch(e=>{console.error(e);process.exitCode=1;});
|