Files
SpineViewer/tests/official-examples.test.js
T
tianmo bb73cc9227 1.1.1版本审核修复
[spine-utils.js (line 35)](/Users/tianmokeji/Desktop/特效/SpineViewer/SpineViewer/js/spine-utils.js:35) 统一拦截绝对路径、..、Windows 盘符、UNC、URI、控制字符和符号链接越界。
跨素材贴图只能使用已枚举、验证且位于 Eagle 素材目录内的文件。
[spine-render.js (line 62)](/Users/tianmokeji/Desktop/特效/SpineViewer/SpineViewer/js/spine-render.js:62) 已删除未经验证的路径备用拼接。
自动缩略图只写当前回调提供的 dest,不再调用接口改写同组素材。
已删除后台整组补刷进程及 relatedDataPaths。
[viewer/spine.html (line 482)](/Users/tianmokeji/Desktop/特效/SpineViewer/SpineViewer/viewer/spine.html:482) 的手动缩略图只针对当前素材,并核对素材 ID、文件名和扩展名。
当前素材已有自定义缩略图时,会提示替换后无法自动恢复并要求确认。
手动生成的中间文件改存系统临时目录,API 失败时不再直接写 Eagle 素材目录。
八种语言文案及 README 已同步。
处理历史日志遗留问题
2026-09-17 13:38:22 +08:00

38 lines
2.4 KiB
JavaScript

'use strict';
const fs=require('fs'),path=require('path'),assert=require('assert/strict');
const render=require('../js/spine-render'),utils=require('../js/spine-utils'),clipping=require('../js/spine-canvas-clipping');
const root=path.resolve(process.argv[2]||path.join(__dirname,'.fixtures'));
(async()=>{
let files=0,poses=0;
for(const version of ['3.4','3.5','3.6','3.7','3.8','4.0','4.1','4.2','4.3']){
const spine=render.loadRuntime(version);
for(const name of fs.readdirSync(path.join(root,version)).filter(n=>!n.startsWith('.')&&fs.statSync(path.join(root,version,n)).isDirectory())){
const dir=path.join(root,version,name),atlasPath=path.join(dir,name+'.atlas');
if(!fs.existsSync(atlasPath))continue;
const text=fs.readFileSync(atlasPath,'utf8');
const texturePaths={};
for(const page of utils.parseAtlasPages(text)){
const texture=utils.resolveAtlasTextureFile(dir,page);assert(texture,version+'/'+name+' missing '+page);texturePaths[page]=texture;
}
const atlas=await render.buildTextureAtlas(spine,text,texturePaths);
for(const file of fs.readdirSync(dir).filter(n=>/\.(json|skel)$/.test(n))){
const source=path.join(dir,file),detected=utils.detectVersion(source);
assert.equal(utils.chooseRuntimeDir(detected,file.endsWith('.skel')),version,source+' version');
const data=render.readSkeletonData(spine,source,atlas,file.endsWith('.skel'));
assert(data.bones.length>0,source);
const renderer=clipping.createRenderer(spine,{globalAlpha:1,globalCompositeOperation:'source-over'},version);
let triangles=0;renderer.drawTriangle=(_image,...coords)=>{assert(coords.every(Number.isFinite),source+' geometry');triangles++};
for(const animation of data.animations){
const skeleton=new spine.Skeleton(data),state=new spine.AnimationState(new spine.AnimationStateData(data));
state.setAnimation(0,animation.name,true);state.update(animation.duration*0.37);state.apply(skeleton);
if(spine.Physics)skeleton.updateWorldTransform(spine.Physics.update);else skeleton.updateWorldTransform();
renderer.draw(skeleton);poses++;
}
assert(triangles>0||data.animations.length===0,source+' empty geometry');files++;
}
}
console.log('PASS '+version+' official exports');
}
console.log('PASS: '+files+' skeleton files, '+poses+' animation/render checks; all atlas pages present');
})().catch(error=>{console.error(error);process.exitCode=1});