feat: local Bootstrap Vue ECharts dashboards and universal genealogy write scope

This commit is contained in:
Codex
2026-10-08 22:41:19 +08:00
parent d8826152c6
commit fb87ae4de9
33 changed files with 980 additions and 110 deletions
+9 -1
View File
@@ -4,6 +4,14 @@ const source=name=>fs.readFileSync('web/'+name,'utf8');
function section(file,start,end){const s=source(file),a=s.indexOf(start),b=s.indexOf(end,a+start.length);assert.ok(a>=0&&b>a);return s.slice(a,b);}
const apiSource=section('app.js','async function api(','function toast(');
const refreshSource=section('app.js','async function refresh(','async function auth(');
test('genealogy UI applies write scope independently of viewing reference',()=>{
const family={canEdit:true,editScope:{limit:5,levels:{self:0,near:-5,far:-6},editablePersonIds:['self','near']},links:[{id:'edge',fromId:'near',toId:'far'}],events:[{id:'event',personIds:['far']}]};
const c=vm.createContext({family});vm.runInContext(section('family.js','function familyEditable(','function editablePeopleOptions('),c);
assert.equal(c.familyEditable('person','near'),true);assert.equal(c.familyEditable('person','far'),false);
assert.equal(c.familyEditable('link','edge'),false);assert.equal(c.familyEditable('event','event'),false);
assert.match(c.familyScopeLabel('far'),/只读/);family.selfId='far';assert.equal(c.familyEditable('person','far'),false);
family.canEdit=false;assert.equal(c.familyEditable('person','near'),false);
});
function context(extra={}){return vm.createContext({sessionGeneration:0,refreshGeneration:0,authGeneration:0,loggedIn:true,currentUser:{id:'a'},state:{},Error,...extra});}
function deferred(){let resolve,reject;const promise=new Promise((a,b)=>{resolve=a;reject=b;});return {promise,resolve,reject};}
@@ -57,7 +65,7 @@ test('archived person reached from an event is revealed before graph location',(
test('event default save preserves published state and exposes explicit withdrawal',()=>{
const controls=new Map(),form={dataset:{},querySelector:s=>{if(!controls.has(s))controls.set(s,{});return controls.get(s);}};
const elements=new Map([['#event-form',form]]);const $=s=>{if(!elements.has(s))elements.set(s,{showModal(){},close(){}});return elements.get(s);};
const c=context({family:{canEdit:true,revision:3,people:[],events:[{id:'published',status:'published',personIds:[]}]},$,fillForm:()=>{},initDateFields:()=>{},esc:String});
const c=context({family:{canEdit:true,revision:3,people:[],events:[{id:'published',status:'published',personIds:[]}]},familyEditable:()=>true,$,fillForm:()=>{},initDateFields:()=>{},esc:String});
vm.runInContext(section('family-events.js','function editFamilyEvent(',"$('#event-form').addEventListener"),c);
c.editFamilyEvent('published');assert.equal(form.querySelector('[data-save-event]').value,'published');assert.equal(form.querySelector('[data-save-event]').textContent,'保存修改');assert.equal(form.querySelector('[data-publish-event]').hidden,true);assert.equal(form.querySelector('[data-withdraw-event]').hidden,false);
c.editFamilyEvent();assert.equal(form.querySelector('[data-save-event]').value,'draft');assert.equal(form.querySelector('[data-withdraw-event]').hidden,true);