Files
UnrealEngine/Engine/Source/ThirdParty/MaterialX/MaterialX-1.38.10/javascript/MaterialXTest/customBindings.spec.js
2025-05-18 13:04:45 +08:00

219 lines
9.0 KiB
JavaScript

import { expect } from 'chai';
import Module from './_build/JsMaterialXCore.js';
import { getMtlxStrings } from './testHelpers';
describe('Custom Bindings', () =>
{
const examplesPath = '../../resources/Materials/Examples/StandardSurface';
let mx;
before(async () =>
{
mx = await Module();
});
it('Optional parameters work as expected', () =>
{
const doc = mx.createDocument();
// Call a method without optional argument
const nodeGraph = doc.addNodeGraph();
expect(nodeGraph).to.be.instanceof(mx.NodeGraph);
expect(nodeGraph.getName()).to.equal('nodegraph1'); // Auto-constructed default value
// Call a method with optional argument
const nodeGraph2 = doc.addNodeGraph('myGraph');
expect(nodeGraph2).to.be.instanceof(mx.NodeGraph);
expect(nodeGraph2.getName()).to.equal('myGraph');
// Call a method that requires at least one parameter
const node = nodeGraph.addNode('node');
expect(node).to.be.instanceof(mx.Node);
// Omitting non-optional parameter should throw
expect(() => { nodeGraph.addNode(); }).to.throw;
});
it('Vector <-> Array conversion', () =>
{
// Functions that return vectors in C++ should return an array in JS
const doc = mx.createDocument();
const nodeGraph = doc.addNodeGraph();
doc.addNodeGraph();
const nodeGraphs = doc.getNodeGraphs();
expect(nodeGraphs).to.be.an.instanceof(Array);
expect(nodeGraphs.length).to.equal(2);
// Elements fetched through the vector -> array conversion should be editable and changes should be reflected
// in the original objects.
// Note: We cannot simply compare these objects for equality, since they're separately constructed pointers
// to the same object.
const backdrop = nodeGraph.addBackdrop();
const backDrops = nodeGraphs[0].getBackdrops();
expect(backDrops.length).to.equal(1);
nodeGraphs[0].addBackdrop();
expect(nodeGraph.getBackdrops().length).to.equal(2);
// Functions that expect vectors as parameters in C++ should accept arrays in JS
// Built-in types (strings)
const pathSegments = ['path', 'to', 'something'];
const namePath = mx.createNamePath(pathSegments);
expect(namePath).to.equal(pathSegments.join(mx.NAME_PATH_SEPARATOR));
// Complex (smart pointer) types
const node1 = nodeGraph.addNode('node');
const node2 = nodeGraph.addNode('node');
const node3 = nodeGraph.addNode('node', 'anotherNode');
backdrop.setContainsElements([node1, node2, node3]);
const nodes = backdrop.getContainsElements();
expect(nodes.length).to.equal(3);
expect(nodes[0].getName()).to.equal('node1'); // Name auto-constructed from category
expect(nodes[1].getName()).to.equal('node2'); // Name auto-constructed from category
expect(nodes[2].getName()).to.equal('anotherNode'); // Name set explicitly at creation time
});
it('C++ exception handling', () =>
{
// Exceptions that are thrown and caught in C++ shouldn't bubble up to JS
const doc = mx.createDocument();
const nodeGraph1 = doc.addNodeGraph();
const nodeGraph2 = doc.addNodeGraph();
nodeGraph1.setInheritsFrom(nodeGraph2);
nodeGraph2.setInheritsFrom(nodeGraph1);
expect(nodeGraph1.hasInheritanceCycle()).to.not.throw;
expect(nodeGraph1.hasInheritanceCycle()).to.be.true;
// Exceptions that are not caught in C++ should throw with an exception pointer
nodeGraph1.addNode('node', 'node1');
expect(() => { nodeGraph1.addNode('node', 'node1'); }).to.throw;
try
{
nodeGraph1.addNode('node', 'node1');
} catch (errPtr)
{
expect(errPtr).to.be.a('number');
expect(mx.getExceptionMessage(errPtr)).to.be.a('string');
}
});
it('getReferencedSourceUris', async () =>
{
const doc = mx.createDocument();
const filename = 'standard_surface_look_brass_tiled.mtlx';
await mx.readFromXmlFile(doc, filename, examplesPath);
const sourceUris = doc.getReferencedSourceUris();
expect(sourceUris).to.be.instanceof(Array);
expect(sourceUris.length).to.equal(3);
expect(sourceUris[0]).to.be.a('string');
expect(sourceUris.includes('standard_surface_brass_tiled.mtlx')).to.be.true;
});
it('Should invoke correct instance of \'validate\'', () =>
{
// We check whether the correct function is called by provoking an error message that is specific to the
// function that we expect to be called.
const message = {};
// Should invoke Document::validate.
const doc = mx.createDocument();
expect(doc.validate()).to.be.true;
doc.removeAttribute(mx.InterfaceElement.VERSION_ATTRIBUTE);
expect(doc.validate()).to.be.true;
// Should invoke Node::validate
const node = doc.addNode('node');
expect(node.validate()).to.be.true;
node.setCategory('');
expect(node.validate()).to.be.false;
expect(node.validate(message)).to.be.false;
expect(message.message).to.include('Node element is missing a category');
// Should invoke inherited ValueElement::validate
const token = new mx.Token(node, 'token');
expect(token.validate()).to.be.true;
token.setUnitType('bogus');
expect(token.validate()).to.be.false;
expect(token.validate(message)).to.be.false;
expect(message.message).to.include('Unit type definition does not exist in document')
});
it('StringResolver name substitution getters', () =>
{
const fnTestData = {
fnKey: 'fnValue',
fnKey1: 'fnValue1'
};
const fnTestKeys = Object.keys(fnTestData);
const gnTestData = {
gnKey: 'gnValue',
gnKey1: 'gnValue1'
};
const gnTestKeys = Object.keys(gnTestData);
const resolver = mx.StringResolver.create();
resolver.setFilenameSubstitution(fnTestKeys[0], fnTestData[fnTestKeys[0]]);
resolver.setFilenameSubstitution(fnTestKeys[1], fnTestData[fnTestKeys[1]]);
const fnSubs = resolver.getFilenameSubstitutions();
expect(fnSubs).to.be.instanceof(Object);
expect(Object.keys(fnSubs).length).to.equal(2);
expect(fnSubs).to.deep.equal(fnTestData);
resolver.setGeomNameSubstitution(gnTestKeys[0], gnTestData[gnTestKeys[0]]);
resolver.setGeomNameSubstitution(gnTestKeys[1], gnTestData[gnTestKeys[1]]);
const gnSubs = resolver.getGeomNameSubstitutions();
expect(gnSubs).to.be.instanceof(Object);
expect(Object.keys(gnSubs).length).to.equal(2);
expect(gnSubs).to.deep.equal(gnTestData);
});
it('getShaderNodes', async () =>
{
const doc = mx.createDocument();
const fileNames = ['standard_surface_marble_solid.mtlx'];
const mtlxStrs = getMtlxStrings(fileNames, examplesPath);
await mx.readFromXmlString(doc, mtlxStrs[0]);
let matNodes = doc.getMaterialNodes();
expect(matNodes.length).to.equal(1);
const matNode = matNodes[0];
// Should return a surface shader node but no displacement shader node
let shaderNodes = mx.getShaderNodes(matNode);
expect(shaderNodes).to.be.instanceof(Array);
expect(shaderNodes.length).to.equal(1);
expect(shaderNodes[0].getType()).to.equal(mx.SURFACE_SHADER_TYPE_STRING);
shaderNodes = mx.getShaderNodes(matNode, mx.DISPLACEMENT_SHADER_TYPE_STRING);
expect(shaderNodes).to.be.instanceof(Array);
expect(shaderNodes.length).to.equal(0);
});
it('createValidName', () =>
{
const testString = '_Note_:Please,turn.this+-into*1#valid\nname for_me';
const replaceRegex = /[^a-zA-Z0-9_:]/g
expect(mx.createValidName(testString)).to.equal(testString.replace(replaceRegex, '_'));
expect(mx.createValidName(testString, '-')).to.equal(testString.replace(replaceRegex, '-'));
});
it('getVersionIntegers', () =>
{
const versionStringArr = mx.getVersionString().split('.').map((value) => parseInt(value, 10));
// Global getVersionIntegers
const globalVersion = mx.getVersionIntegers();
expect(globalVersion).to.be.instanceof(Array);
expect(globalVersion.length).to.equal(3);
expect(globalVersion).to.deep.equal(versionStringArr);
// Document.getVersionIntegers
versionStringArr.pop();
const doc = mx.createDocument();
const docVersion = doc.getVersionIntegers();
expect(docVersion).to.be.instanceof(Array);
expect(docVersion.length).to.equal(2);
expect(docVersion).to.deep.equal(versionStringArr);
// InterfaceElement.getVersionIntegers (via NodeDef)
// TODO: This function can currently not be called, since we have a linker issue that messes up this function.
});
});