improvements
This commit is contained in:
@@ -30,6 +30,7 @@ function harness(capture = false) {
|
||||
add(undo) { assert.equal(undo, false); Texture.all.push(this); }
|
||||
}
|
||||
const context = {
|
||||
Buffer, Timeline: { animation: null },
|
||||
Project: { uuid: 'original' }, Group, Cube: { all: [] }, Texture, Outliner: { elements: [] },
|
||||
Plugin: { register() {} }, requireNativeModule: require,
|
||||
localStorage: { getItem: (key) => storage.get(key), setItem: (key, value) => storage.set(key, value) },
|
||||
@@ -234,7 +235,8 @@ test('short clip names are inferred only for unambiguous actors; repeated entiti
|
||||
|
||||
test('complete import prepares all stages, actor models, local textures and definition fields without touching the current project', async (t) => {
|
||||
const f = fixtures(t), h = harness(true), source = f.makePack('source');
|
||||
const def = { actors: [{ label: 'wolf', entity_types: ['minecraft:wolf', 'minecraft:fox'], actor_tags: ['gender.male'] }],
|
||||
const def = { actors: [{ label: 'wolf', entity_types: ['minecraft:wolf', 'minecraft:fox'], actor_tags: ['gender.male'],
|
||||
prop_right: 'minecraft:carrot' }],
|
||||
animators: ['Creator'], content_tags: ['example'], stages: [{ stage: 1, non_peak: true }, { stage: 2 }] };
|
||||
const defFile = f.write(path.join(source.root, 'data/test/afw_animdefs/example.json'), def);
|
||||
f.write(path.join(source.root, 'assets/test/geckolib/animations/afw/example.animation.json'), { animations: {
|
||||
@@ -243,6 +245,10 @@ test('complete import prepares all stages, actor models, local textures and defi
|
||||
} });
|
||||
f.model(source, 'test:source/entity/wolf.m', { afw_texture: 'test:textures/wolf.png' });
|
||||
f.write(path.join(source.root, 'assets/test/textures/wolf.png'), 'local image');
|
||||
f.write(path.join(source.root, 'assets/minecraft/items/carrot.json'), { model: { type: 'minecraft:model', model: 'minecraft:item/carrot' } });
|
||||
f.write(path.join(source.root, 'assets/minecraft/models/item/carrot.json'), {
|
||||
parent: 'minecraft:builtin/generated', textures: { layer0: 'minecraft:item/carrot' } });
|
||||
f.write(path.join(source.root, 'assets/minecraft/textures/item/carrot.png'), 'image');
|
||||
h.context.selectedFile = { path: defFile };
|
||||
h.context.requireNativeModule = (name) => name === 'fs' ? scopedFilesystem() : path;
|
||||
await h.automaticallyImportAnimation();
|
||||
@@ -260,6 +266,7 @@ test('complete import prepares all stages, actor models, local textures and defi
|
||||
assert.deepEqual(Array.from(plan.imported.stageNames), ['example.p1', 'example.p2']);
|
||||
assert.ok(plan.imported.fileObj.animations['example.p2'].bones.actor1_body);
|
||||
assert.equal(plan.definition.actors[0].label, 'wolf');
|
||||
assert.equal(Object.values(plan.propEntries)[0].item, 'minecraft:carrot');
|
||||
assert.equal(plan.definition.stages[1].non_peak, undefined);
|
||||
assert.equal(h.undo.length, 0);
|
||||
assert.ok(h.storage.get('animationdirector_pack_folders').includes(source.root.replace(/\\/g, '\\\\')));
|
||||
@@ -380,6 +387,7 @@ test('missing models can be supplied manually, and their locally supplied textur
|
||||
test('batch import uses a new project, one undo entry, actor prefixes and texture override bindings', () => {
|
||||
const h = harness();
|
||||
const plan = { parsed: { animationId: 'example' }, blockEntry: null,
|
||||
propEntries: { '["example","wolf","right",0]': { animation: 'example', item: 'minecraft:carrot', mode: 'item' } },
|
||||
definition: { actors: [{ label: 'wolf', entity_types: ['minecraft:wolf'] }], animators: ['Creator'], stages: [{ stage: 1 }] },
|
||||
imported: { stageNames: ['example.p1'], fileObj: { animations: { 'example.p1': { bones: { actor1_body: { rotation: [1, 2, 3] } } } } } },
|
||||
rows: [{ label: 'wolf', json: { afw_bone_textures: { body: 'test:textures/wolf.png' } },
|
||||
@@ -394,6 +402,7 @@ test('batch import uses a new project, one undo entry, actor prefixes and textur
|
||||
const override = JSON.parse(h.context.Project.multiactor_bone_texture_overrides);
|
||||
assert.equal(override[h.context.Group.all[0].uuid].texture, 'test:textures/wolf.png');
|
||||
assert.ok(JSON.parse(h.context.Project.animationdirector_animdef_definitions).example);
|
||||
assert.equal(JSON.parse(h.context.Project.animationdirector_prop_previews)['["example","wolf","right",0]'].item,'minecraft:carrot');
|
||||
});
|
||||
|
||||
test('unexpected animation-load failures cancel the batch rather than keeping a partial import', () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
const vm = require('node:vm');
|
||||
@@ -83,7 +84,7 @@ function harness() {
|
||||
on(names, listener) { for (const name of names.split(' ')) events.set(name, listener); },
|
||||
addCSS() { return { delete() {} }; },
|
||||
showQuickMessage() {}, showMessageBox(options) { errors.push(options.message); } },
|
||||
console,
|
||||
Buffer, requireNativeModule: require, console,
|
||||
};
|
||||
vm.runInNewContext(source, context);
|
||||
return { ...context.blockTest, context, project, dialogs, exports, errors, edits, events, previewEvents, timers, previewUpdates, menus, Group, Cube };
|
||||
@@ -173,13 +174,15 @@ test('import prefills generator fields, builds guides and retains fields without
|
||||
test('import maps explicit labels before positional fallbacks and updates actor references', () => {
|
||||
const h = harness();
|
||||
const source = { actors: [{ label: 'wolf', injector: 'V', injects_into: 'bottom' },
|
||||
{ label: 'bottom', receiver: true }], stages: [{ stage: 1 }, { stage: 2 }], position_anchor_actor: 'bottom' };
|
||||
{ label: 'bottom', receiver: true }], stages: [{ stage: 1, props: { bottom: { prop_right: 'minecraft:carrot' } } },
|
||||
{ stage: 2 }], position_anchor_actor: 'bottom' };
|
||||
const definition = h.prepareAnimationDefinitionImport(source, importAnalysis());
|
||||
assert.equal(definition.actors[0].label, 'player');
|
||||
assert.equal(definition.actors[0].receiver, true);
|
||||
assert.equal(definition.actors[1].label, 'wolf');
|
||||
assert.deepEqual(Array.from(definition.actors[1].injects_into), ['player']);
|
||||
assert.equal(definition.position_anchor_actor, 'player');
|
||||
assert.equal(definition.stages[0].props.player.prop_right, 'minecraft:carrot');
|
||||
assert.equal(source.actors[1].label, 'bottom', 'Input JSON is not mutated');
|
||||
const positional = h.prepareAnimationDefinitionImport({ actors: [{ receiver: true }, { injector: 'V' }],
|
||||
stages: [{ stage: 1 }, { stage: 2 }] }, importAnalysis());
|
||||
@@ -187,6 +190,25 @@ test('import maps explicit labels before positional fallbacks and updates actor
|
||||
assert.equal(positional.actors[1].label, 'wolf');
|
||||
});
|
||||
|
||||
test('manual definition import embeds item prop previews in the same undoable edit', (t) => {
|
||||
const h = harness(), root = fs.mkdtempSync(path.join(os.tmpdir(),'ad-manual-props-'));
|
||||
t.after(() => fs.rmSync(root,{recursive:true,force:true}));
|
||||
const write=(relative,data)=>{ const file=path.join(root,relative); fs.mkdirSync(path.dirname(file),{recursive:true});
|
||||
fs.writeFileSync(file,typeof data==='string'?data:JSON.stringify(data)); };
|
||||
write('assets/minecraft/items/carrot.json',{model:{type:'minecraft:model',model:'minecraft:item/carrot'}});
|
||||
write('assets/minecraft/models/item/carrot.json',{parent:'minecraft:builtin/generated',textures:{layer0:'minecraft:item/carrot'}});
|
||||
write('assets/minecraft/textures/item/carrot.png','image');
|
||||
h.applyAnimationDefinitionImport({id:'test'},importAnalysis(),{
|
||||
actors:[{label:'player',prop_right:'minecraft:carrot'},{label:'wolf'}],
|
||||
stages:[{stage:1},{stage:2,props:{player:{prop_right:null}}}],
|
||||
},[root]);
|
||||
const previews=JSON.parse(h.project.animationdirector_prop_previews);
|
||||
assert.equal(previews['["test","player","right",0]'].item,'minecraft:carrot');
|
||||
assert.equal(previews['["test","player","right",2]'].mode,'hidden');
|
||||
assert.equal(h.edits.at(-1).aspects.prop_preview_edit,true);
|
||||
assert.equal(h.edits.at(-1).post.prop_preview_edit,true);
|
||||
});
|
||||
|
||||
test('invalid imports leave definitions and guides unchanged', () => {
|
||||
const h = harness();
|
||||
h.rebuildBlockRequirementGuides('test', h.readBlockRequirementForm(wallForm()));
|
||||
|
||||
+53
-13
@@ -11,8 +11,8 @@ function harness() {
|
||||
' Plugin.register(PLUGIN_ID, {', `globalThis.props = { indexPropZip, propResourcePath, openPropSource,
|
||||
loadPropAsset, resolvePropModel, propTextureId, propDisplayTransform, manualPropModel,
|
||||
writePropDefinition, effectivePropEntry, effectivePropItem, propEntryKey, extrudePropPixels, discoverPropSources,
|
||||
buildPropMesh, refreshPropPreviews, clearPropMeshes, propFaceVertices, propDefaultUv, propItemOptions,
|
||||
applyPropPreview, createPropDialog, showPropDialog, readPropPreviews };
|
||||
buildPropMesh, refreshPropPreviews, clearPropMeshes, propFaceVertices, propDefaultUv,
|
||||
applyPropPreview, createPropDialog, showPropDialog, readPropPreviews, importPropPreviews };
|
||||
Plugin.register(PLUGIN_ID, {`);
|
||||
const context = { Buffer, requireNativeModule: require, console, Plugin: { register() {} },
|
||||
Project: { uuid: 'test' }, Group: { all: [] }, Animation: { all: [] }, localStorage: { getItem() { return null; } } };
|
||||
@@ -161,16 +161,6 @@ test('asset discovery reuses Asset Browser cache without requiring the plugin',
|
||||
assert.equal(sources[0].label,'Asset Browser: 1.21.11');
|
||||
});
|
||||
|
||||
test('item picker includes entries after the first hundred and filters the full source list', () => {
|
||||
const h = harness();
|
||||
const items = Array.from({ length: 1505 }, (_, index) => `minecraft:item_${String(index).padStart(4, '0')}`);
|
||||
const all = h.propItemOptions(items, '');
|
||||
assert.equal(Object.keys(all).length, 1506);
|
||||
assert.equal(all['minecraft:item_1504'], 'minecraft:item_1504');
|
||||
const searched = h.propItemOptions(items, ' ITEM_1504 ');
|
||||
assert.deepEqual(Object.keys(searched), ['', 'minecraft:item_1504']);
|
||||
});
|
||||
|
||||
test('Item Props uses native dialog form fields and Apply/Cancel actions', () => {
|
||||
const h = harness();
|
||||
let configuration;
|
||||
@@ -189,7 +179,57 @@ test('Item Props uses native dialog form fields and Apply/Cancel actions', () =>
|
||||
assert.equal(configuration.form.jar.type,'file');
|
||||
assert.equal(configuration.form.folder.type,'folder');
|
||||
assert.equal(configuration.form.texture.type,'file');
|
||||
assert.equal(typeof configuration.form.item_choice.options,'function');
|
||||
assert.equal(configuration.form.item_search,undefined);
|
||||
assert.equal(configuration.form.item_choice.type,'select');
|
||||
});
|
||||
|
||||
test('item dropdown includes the entire asset source alongside editable Item ID', (t) => {
|
||||
const h = harness(), root = fs.mkdtempSync(path.join(os.tmpdir(),'ad-item-picker-'));
|
||||
t.after(() => fs.rmSync(root,{recursive:true,force:true}));
|
||||
const items = path.join(root,'assets','minecraft','items');
|
||||
fs.mkdirSync(items,{recursive:true});
|
||||
for(let index=0;index<105;index++) fs.writeFileSync(path.join(items,`item_${String(index).padStart(3,'0')}.json`),'{}');
|
||||
h.context.Animation.selected = { name:'test.p1' };
|
||||
h.context.localStorage = { getItem() { return root; } };
|
||||
let configuration;
|
||||
h.context.Dialog = class {
|
||||
constructor(options){ configuration=options; }
|
||||
};
|
||||
h.createPropDialog();
|
||||
const options = configuration.form.item_choice.options();
|
||||
assert.equal(Object.keys(options).length,106);
|
||||
assert.equal(options['minecraft:item_104'],'minecraft:item_104');
|
||||
assert.equal(configuration.form.item.value,'minecraft:carrot');
|
||||
});
|
||||
|
||||
test('imported definitions create default and stage item previews without losing hidden overrides', (t) => {
|
||||
const h = harness(), root = fs.mkdtempSync(path.join(os.tmpdir(),'ad-imported-props-'));
|
||||
t.after(() => fs.rmSync(root,{recursive:true,force:true}));
|
||||
for (const item of ['carrot','stick']) {
|
||||
const write = (relative,data) => {
|
||||
const file=path.join(root,relative); fs.mkdirSync(path.dirname(file),{recursive:true});
|
||||
fs.writeFileSync(file,typeof data==='string'?data:JSON.stringify(data));
|
||||
};
|
||||
write(`assets/minecraft/items/${item}.json`,{model:{type:'minecraft:model',model:`minecraft:item/${item}`}});
|
||||
write(`assets/minecraft/models/item/${item}.json`,{parent:'minecraft:builtin/generated',textures:{layer0:`minecraft:item/${item}`}});
|
||||
write(`assets/minecraft/textures/item/${item}.png`,'png bytes');
|
||||
}
|
||||
const definition={actors:[{label:'player',prop_right:'minecraft:carrot'}],stages:[
|
||||
{stage:1},{stage:2,props:{player:{prop_right:'minecraft:stick'}}},{stage:3,props:{player:{prop_right:null}}}]};
|
||||
const oldKey=h.propEntryKey('other','player','left',0);
|
||||
const result=h.importPropPreviews('example',definition,[root],{[oldKey]:{animation:'other'}});
|
||||
assert.equal(result.warnings.length,0);
|
||||
assert.equal(result.entries[oldKey].animation,'other');
|
||||
const get=(stage)=>result.entries[h.propEntryKey('example','player','right',stage)];
|
||||
assert.equal(get(0).item,'minecraft:carrot');
|
||||
assert.equal(get(0).asset.parts[0].generated,true);
|
||||
assert.equal(get(1),undefined);
|
||||
assert.equal(get(2).item,'minecraft:stick');
|
||||
assert.equal(get(3).mode,'hidden');
|
||||
const missing=h.importPropPreviews('example',{actors:[{label:'player',prop_left:'minecraft:missing'}],stages:[{stage:1}]},[root],result.entries);
|
||||
assert.match(missing.warnings[0],/minecraft:missing/);
|
||||
assert.equal(missing.entries[h.propEntryKey('example','player','left',0)],undefined);
|
||||
assert.equal(missing.entries[oldKey].animation,'other');
|
||||
});
|
||||
|
||||
module.exports = { harness };
|
||||
|
||||
Reference in New Issue
Block a user