Files
meguri/tests/browser-binding-path.cjs
Aleksandr BogomiakovandClaude 38105c65c2 Merge NEXT: planning, generation map and portable configuration on top of the OIDC access model
NEXT predates the access work, so the identity layer follows master: access comes from
configured groups, instance administrators replace the site administration, and accounts are
created by SSO or by an administrator. Kept from NEXT: manual publication with the Review
changes panel and shared generator drafts, portable import/export (meguri/v2), the layered
generation map with binding provenance, JSM provider changes, contact/approval semantics and
the account-owned Person. Dropped: site administration, local sign-up, and the mandatory
unique email with its contact/account migrations. The manual-publication migration is
renumbered m_0010 after m_0009_access.

Accounts without an email still get a Person in every workspace they reach, a changed sign-in
email updates the account's person, and an account that loses access keeps its linked person so
schedules stay valid. Instance administrators may use Sync and shared drafts.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-07 16:06:10 +01:00

44 lines
2.9 KiB
JavaScript

const assert=require('node:assert/strict');
const fs=require('node:fs');
const {launchBrowser,isolatedUser,signIn}=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}});
const errors=[];page.on('pageerror',e=>errors.push(e.message));
// Sandbox 12 belongs to this suite; workspaces cannot be created, so it starts empty.
const tester=await isolatedUser({sandbox:12,prefix:'binding-path',display:'Path tester'});
await signIn(page,tester.username,tester.password);
await page.getByLabel('Workspace',{exact:true}).waitFor();
const dialog=page.getByRole('dialog');
await page.evaluate(()=>{
const a=Alpine.$data(document.body),root=a.newNode('dictionary'),owners=a.newNode('list',null,'owners');
owners.entries=[a.newNode('text','First owner'),a.newNode('text','Second owner')];root.entries=[owners];
const variable={id:crypto.randomUUID(),name:'Project',node:root};a.generationDraft.variables.push(variable);
a.editGenerationRule();const block=a.newBlock('rotation');block.fields.name={kind:'variable',variable:variable.id,path:[]};a.form.blocks=[block];
});
const open=()=>page.evaluate(()=>{const a=Alpine.$data(document.body);a.editBinding(a.form.blocks[0].fields.name,'text','Name');});
await open();
const popup=page.locator('.binding-popover'),path=popup.getByLabel('Nested field or position',{exact:true});
await path.pressSequentially('owners / ',{delay:30});
assert.equal(await path.inputValue(),'owners / ');
await path.pressSequentially('1',{delay:30});
await page.waitForFunction(()=>Alpine.$data(document.body).bindingValuePreview().text.includes('First owner'));
assert.deepEqual(await page.evaluate(()=>Alpine.$data(document.body).form.blocks[0].fields.name.path),['owners','1']);
await path.press('Backspace');await path.pressSequentially('2');
await page.waitForFunction(()=>Alpine.$data(document.body).bindingValuePreview().text.includes('Second owner'));
await popup.getByRole('button',{name:'Done',exact:true}).click();await open();
assert.equal(await path.inputValue(),'owners / 2');
await page.setViewportSize({width:390,height:844});
fs.mkdirSync('test-results',{recursive:true});await page.screenshot({path:'test-results/binding-path-mobile.png'});
assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));
await popup.getByLabel('Value source',{exact:true}).selectOption('literal');
await popup.getByLabel('Value source',{exact:true}).selectOption('variable');
assert.equal(await path.inputValue(),'');
assert.deepEqual(errors,[]);
console.log('PASS: typed nested path, live resolved values, edits, reopening, mobile and source reset');
}finally{await browser.close();}
})().catch(e=>{console.error(e);process.exitCode=1;});