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. }); });