teach/test/avatar.test.js
Ramon d0522d7e33
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 58s
feat: verbeter anatomie en accessoires van avatars (v0.4.46-beta)
2026-07-21 23:25:12 +02:00

114 lines
4.9 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {readFile} from 'node:fs/promises';
import {runInNewContext} from 'node:vm';
async function loadAvatarRenderer(){
const source=await readFile('public/js/avatar.js','utf8');
const cutoff=source.indexOf('/* bouwt de interactieve kiezer');
assert.ok(cutoff>0,'rendergedeelte van avatar.js ontbreekt');
const created=[];
const context={
T:key=>key,
document:{
createElementNS(){
const element={
attributes:{},
innerHTML:'',
setAttribute(name,value){this.attributes[name]=String(value);},
};
created.push(element);
return element;
},
},
};
runInNewContext(source.slice(0,cutoff)+
';globalThis.__avatar={renderAvatarFace,avatarBodyMarkup,bodyAccessories:AVATAR_BODY_ACC_MARKUP,headAccessories:AVATAR_HEAD_ACC_MARKUP};',
context);
return {api:context.__avatar,created};
}
const base={
species:'dino',
bodyColor:'#3fae6a',
hairStyle:'none',
hairColor:'#5b3a29',
accessoryHead:'none',
accessoryBody:'none',
};
test('avatars hebben verbonden en herkenbare lichaamsdelen per soort',async()=>{
const {api}=await loadAvatarRenderer();
for(const species of ['dino','monster','animal']){
const markup=api.avatarBodyMarkup(species,base.bodyColor);
assert.equal((markup.match(/av-arm av-arm-/g)||[]).length,2,species+' mist twee armen');
assert.equal((markup.match(/av-leg av-leg-/g)||[]).length,2,species+' mist twee benen');
for(const part of ['av-neck','av-torso','av-head','av-eye']){
assert.ok(markup.includes(part),species+' mist '+part);
}
assert.ok(markup.indexOf('av-arm')<markup.indexOf('av-torso'),species+'-armen moeten achter de romp aansluiten');
assert.ok(markup.indexOf('av-leg')<markup.indexOf('av-torso'),species+'-benen moeten onder de romp aansluiten');
}
const dino=api.avatarBodyMarkup('dino',base.bodyColor);
const monster=api.avatarBodyMarkup('monster',base.bodyColor);
const animal=api.avatarBodyMarkup('animal',base.bodyColor);
assert.ok(dino.indexOf('av-tail')<dino.indexOf('av-head'));
assert.ok(dino.indexOf('av-crest')<dino.indexOf('av-head'));
assert.equal((monster.match(/av-horn/g)||[]).length,2);
assert.equal((animal.match(/av-ear av-ear-/g)||[]).length,2);
});
test('lichaamsaccessoires gebruiken anatomisch correcte voor- en achterlagen',async()=>{
const {api}=await loadAvatarRenderer();
for(const id of ['none','scarf','cape','badge','necklace','backpack']){
assert.equal(typeof api.bodyAccessories[id].back,'function',id+' mist achterlaag');
assert.equal(typeof api.bodyAccessories[id].front,'function',id+' mist voorlaag');
}
const cape=api.renderAvatarFace({...base,accessoryBody:'cape'},'lg').innerHTML;
assert.ok(cape.indexOf('av-cape\"')<cape.indexOf('av-torso\"'));
assert.ok(cape.indexOf('av-cape-clasp')>cape.indexOf('av-head\"'));
const backpack=api.renderAvatarFace({...base,accessoryBody:'backpack'},'lg').innerHTML;
assert.ok(backpack.indexOf('av-backpack\"')<backpack.indexOf('av-torso\"'));
assert.ok(backpack.indexOf('av-backpack-strap')>backpack.indexOf('av-head\"'));
for(const [id,part] of [['scarf','av-scarf'],['badge','av-badge'],['necklace','av-necklace']]){
const markup=api.renderAvatarFace({...base,accessoryBody:id},'lg').innerHTML;
assert.ok(markup.indexOf(part)>markup.indexOf('av-head\"'),id+' hoort vóór op het lichaam');
}
});
test('alle avatarcombinaties renderen volledig binnen de verruimde SVG',async()=>{
const {api}=await loadAvatarRenderer();
const species=['dino','monster','animal'];
const hair=['none','spiky','curly','ponytail','mohawk'];
const head=['none','cap','crown','glasses','sunglasses','bow','headphones','partyhat'];
const body=['none','scarf','cape','badge','necklace','backpack'];
for(const speciesId of species){
for(const hairId of hair){
for(const headId of head){
for(const bodyId of body){
const svg=api.renderAvatarFace({
...base,
species:speciesId,
hairStyle:hairId,
accessoryHead:headId,
accessoryBody:bodyId,
},'sm');
assert.equal(svg.attributes.viewBox,'-6 -12 112 124');
assert.equal(svg.attributes.preserveAspectRatio,'xMidYMid meet');
assert.doesNotMatch(svg.innerHTML,/undefined|NaN/);
assert.ok(svg.innerHTML.includes('av-head'));
}
}
}
}
const cap=api.renderAvatarFace({...base,hairStyle:'spiky',hairColor:'#123456',accessoryHead:'cap'},'lg').innerHTML;
const crown=api.renderAvatarFace({...base,hairStyle:'spiky',hairColor:'#123456',accessoryHead:'crown'},'lg').innerHTML;
assert.ok(!cap.includes('#123456'),'pet hoort het bovenhaar af te dekken');
assert.ok(crown.includes('#123456'),'kroon hoort op het haar te rusten');
for(const id of head)assert.equal(typeof api.headAccessories[id],'function');
});