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

279 lines
11 KiB
JavaScript

import Module from './_build/JsMaterialXCore.js';
import { expect } from 'chai';
import { getMtlxStrings } from './testHelpers';
const TIMEOUT = 60000;
describe('XmlIo', () =>
{
let mx;
// These should be relative to cwd
const includeTestPath = 'data/includes';
const libraryPath = '../../libraries/stdlib';
const examplesPath = '../../resources/Materials/Examples';
// TODO: Is there a better way to get these filenames than hardcoding them here?
// The C++ tests load all files in the given directories. This would work in Node, but not in the browser.
// Should we use a pre-test script that fetches the files and makes them available somehow?
const libraryFilenames = ['stdlib_defs.mtlx', 'stdlib_ng.mtlx'];
const exampleFilenames = [
'StandardSurface/standard_surface_brass_tiled.mtlx',
'StandardSurface/standard_surface_brick_procedural.mtlx',
'StandardSurface/standard_surface_carpaint.mtlx',
'StandardSurface/standard_surface_marble_solid.mtlx',
'UsdPreviewSurface/usd_preview_surface_gold.mtlx',
'UsdPreviewSurface/usd_preview_surface_plastic.mtlx',
];
async function readStdLibrary(asString = false)
{
const libs = [];
let iterable = libraryFilenames;
if (asString)
{
const libraryMtlxStrings = getMtlxStrings(libraryFilenames, libraryPath);
iterable = libraryMtlxStrings;
}
for (let file of iterable)
{
const lib = mx.createDocument();
if (asString)
{
await mx.readFromXmlString(lib, file, libraryPath);
} else
{
await mx.readFromXmlFile(lib, file, libraryPath);
}
libs.push(lib);
};
return libs;
}
async function readAndValidateExamples(examples, libraries, readFunc, searchPath = undefined)
{
for (let file of examples)
{
const doc = mx.createDocument();
await readFunc(doc, file, searchPath);
// Import stdlib into the current document and validate it.
for (let lib of libraries)
{
doc.importLibrary(lib);
}
expect(doc.validate()).to.be.true;
// Make sure the document does actually contain something.
let valueElementCount = 0;
const treeIter = doc.traverseTree();
for (const elem of treeIter)
{
if (elem instanceof mx.ValueElement)
{
valueElementCount++;
}
}
expect(valueElementCount).to.be.greaterThan(0);
};
}
before(async () =>
{
mx = await Module();
});
it('Read XML from file', async () =>
{
// Read the standard library
const libs = await readStdLibrary(false);
// Read and validate the example documents.
await readAndValidateExamples(exampleFilenames, libs,
async (document, file, sp) =>
{
await mx.readFromXmlFile(document, file, sp);
}, examplesPath);
// Read the same document twice, and verify that duplicate elements
// are skipped.
const doc = mx.createDocument();
const filename = 'StandardSurface/standard_surface_carpaint.mtlx';
await mx.readFromXmlFile(doc, filename, examplesPath);
const copy = doc.copy();
await mx.readFromXmlFile(doc, filename, examplesPath);
expect(doc.validate()).to.be.true;
expect(copy.equals(doc)).to.be.true;
}).timeout(TIMEOUT);
it('Read XML from string', async () =>
{
// Read the standard library
const libs = await readStdLibrary(true);
// Read and validate each example document.
const examplesStrings = getMtlxStrings(exampleFilenames, examplesPath);
await readAndValidateExamples(examplesStrings, libs,
async (document, file) =>
{
await mx.readFromXmlString(document, file);
});
// Read the same document twice, and verify that duplicate elements
// are skipped.
const doc = mx.createDocument();
const file = examplesStrings[exampleFilenames.indexOf('StandardSurface/standard_surface_carpaint.mtlx')];
await mx.readFromXmlString(doc, file);
const copy = doc.copy();
await mx.readFromXmlString(doc, file);
expect(doc.validate()).to.be.true;
expect(copy.equals(doc)).to.be.true;
}).timeout(TIMEOUT);
it('Read XML with recursive includes', async () =>
{
const doc = mx.createDocument();
await mx.readFromXmlFile(doc, includeTestPath + '/root.mtlx');
expect(doc.getChild('paint_semigloss')).to.exist;
expect(doc.validate()).to.be.true;
});
it('Locate XML includes via search path', async () =>
{
const searchPath = includeTestPath + ';' + includeTestPath + '/folder';
const filename = 'non_relative_includes.mtlx';
const doc = mx.createDocument();
expect(async () => await mx.readFromXmlFile(doc, filename, includeTestPath)).to.throw;
await mx.readFromXmlFile(doc, filename, searchPath);
expect(doc.getChild('paint_semigloss')).to.exist;
expect(doc.validate()).to.be.true;
const doc2 = mx.createDocument();
const mtlxString = getMtlxStrings([filename], includeTestPath);
expect(async () => await mx.readFromXmlString(doc2, mtlxString[0])).to.throw;
await mx.readFromXmlString(doc2, mtlxString[0], searchPath);
expect(doc2.getChild('paint_semigloss')).to.exist;
expect(doc2.validate()).to.be.true;
expect(doc2.equals(doc)).to.be.true;
});
it('Locate XML includes via environment variable', async () =>
{
const searchPath = includeTestPath + ';' + includeTestPath + '/folder';
const filename = 'non_relative_includes.mtlx';
const doc = mx.createDocument();
expect(async () => await mx.readFromXmlFile(doc, includeTestPath + '/' + filename)).to.throw;
mx.setEnviron(mx.MATERIALX_SEARCH_PATH_ENV_VAR, searchPath);
await mx.readFromXmlFile(doc, filename);
mx.removeEnviron(mx.MATERIALX_SEARCH_PATH_ENV_VAR);
expect(doc.getChild('paint_semigloss')).to.exist;
expect(doc.validate()).to.be.true;
const doc2 = mx.createDocument();
const mtlxString = getMtlxStrings([filename], includeTestPath);
expect(async () => await mx.readFromXmlString(doc2, mtlxString[0])).to.throw;
mx.setEnviron(mx.MATERIALX_SEARCH_PATH_ENV_VAR, searchPath);
await mx.readFromXmlString(doc2, mtlxString[0]);
mx.removeEnviron(mx.MATERIALX_SEARCH_PATH_ENV_VAR);
expect(doc2.getChild('paint_semigloss')).to.exist;
expect(doc2.validate()).to.be.true;
expect(doc2.equals(doc)).to.be.true;
});
it('Locate XML includes via absolute search paths', async () =>
{
let absolutePath;
if (typeof window === 'object')
{
// We're in the browser
const cwd = window.location.origin + window.location.pathname;
absolutePath = cwd + '/' + includeTestPath;
} else if (typeof process === 'object')
{
// We're in Node
const nodePath = require('path');
absolutePath = nodePath.resolve(includeTestPath);
}
const doc = mx.createDocument();
await mx.readFromXmlFile(doc, 'root.mtlx', absolutePath);
});
it('Detect XML include cycles', async () =>
{
const doc = mx.createDocument();
expect(async () => await mx.readFromXmlFile(doc, includeTestPath + '/cycle.mtlx')).to.throw;
});
it('Disabling XML includes', async () =>
{
const doc = mx.createDocument();
const readOptions = new mx.XmlReadOptions();
readOptions.readXIncludes = false;
expect(async () => await mx.readFromXmlFile(doc, includeTestPath + '/cycle.mtlx', readOptions)).to.not.throw;
});
it('Write to XML string', async () =>
{
// Read all example documents and write them to an XML string
const searchPath = libraryPath + ';' + examplesPath;
for (let filename of exampleFilenames)
{
const doc = mx.createDocument();
await mx.readFromXmlFile(doc, filename, searchPath);
// Serialize to XML.
const writeOptions = new mx.XmlWriteOptions();
writeOptions.writeXIncludeEnable = false;
const xmlString = mx.writeToXmlString(doc, writeOptions);
// Verify that the serialized document is identical.
const writtenDoc = mx.createDocument();
await mx.readFromXmlString(writtenDoc, xmlString);
expect(writtenDoc).to.eql(doc);
};
});
it('Prepend include tag', () =>
{
const doc = mx.createDocument();
const includePath = "SomePath";
const writeOptions = new mx.XmlWriteOptions();
mx.prependXInclude(doc, includePath);
const xmlString = mx.writeToXmlString(doc, writeOptions);
expect(xmlString).to.include(includePath);
});
// Node only, because we cannot read from a downloaded file in the browser
it('Write XML to file', async () =>
{
const filename = '_build/testFile.mtlx';
const includeRegex = /<xi:include href="(.*)"\s*\/>/g;
const doc = mx.createDocument();
await mx.readFromXmlFile(doc, 'root.mtlx', includeTestPath);
// Write using includes
mx.writeToXmlFile(doc, filename);
// Read written document and compare with the original
const doc2 = mx.createDocument();
await mx.readFromXmlFile(doc2, filename, includeTestPath);
expect(doc2.equals(doc));
// Read written file content and verify that includes are preserved
let fileString = getMtlxStrings([filename], '')[0];
let matches = Array.from(fileString.matchAll(includeRegex));
expect(matches.length).to.be.greaterThan(0);
// Write inlining included content
const writeOptions = new mx.XmlWriteOptions();
writeOptions.writeXIncludeEnable = false;
mx.writeToXmlFile(doc, filename, writeOptions);
// Read written document and compare with the original
const doc3 = mx.createDocument();
await mx.readFromXmlFile(doc3, filename);
expect(doc3.equals(doc));
expect(doc.getChild('paint_semigloss')).to.exist;
// Read written file content and verify that includes are inlined
fileString = getMtlxStrings([filename], '')[0];
matches = Array.from(fileString.matchAll(includeRegex));
expect(matches.length).to.equal(0);
});
});