diff --git a/src/app/NewProject.tsx b/src/app/NewProject.tsx index 7cafbd49..8705f1de 100644 --- a/src/app/NewProject.tsx +++ b/src/app/NewProject.tsx @@ -142,19 +142,31 @@ export class NewProject extends React.Component { + await ensureInitialized(options.wasm); + const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data; + return Project.fromVensim(bytes); + } + + /** + * Check if the WASM module was built with Vensim MDL support. + * Automatically initializes WASM if needed. + * + * @param options Optional WASM configuration + * @returns Promise resolving to true if Project.openVensim() is available + */ + static async hasVensimSupport(options: ProjectOpenOptions = {}): Promise { + await ensureInitialized(options.wasm); + return hasVensimSupport(); + } + /** * Get the internal WASM pointer. For internal use only. */ diff --git a/src/engine2/tests/api.test.ts b/src/engine2/tests/api.test.ts index cf47fe2a..32a613b8 100644 --- a/src/engine2/tests/api.test.ts +++ b/src/engine2/tests/api.test.ts @@ -34,6 +34,15 @@ function loadTestXmile(): Uint8Array { return fs.readFileSync(xmilePath); } +// Load the teacup test model in Vensim MDL format +function loadTestMdl(): Uint8Array { + const mdlPath = path.join(__dirname, '..', '..', '..', 'test', 'test-models', 'samples', 'teacup', 'teacup.mdl'); + if (!fs.existsSync(mdlPath)) { + throw new Error('Required test MDL model not found: ' + mdlPath); + } + return fs.readFileSync(mdlPath); +} + async function openTestProject(): Promise { return Project.open(loadTestXmile()); } @@ -1067,4 +1076,83 @@ describe('High-Level API', () => { project.dispose(); }); }); + + describe('Vensim MDL support', () => { + it('should check hasVensimSupport availability via Project static method', async () => { + // Project.hasVensimSupport() is an async method that ensures WASM is initialized + const supported = await Project.hasVensimSupport(); + expect(typeof supported).toBe('boolean'); + }); + + it('should handle openVensim when support is not available', async () => { + const supported = await Project.hasVensimSupport(); + + if (!supported) { + // When Vensim support is not available, openVensim should throw + const mdlData = loadTestMdl(); + await expect(Project.openVensim(mdlData)).rejects.toThrow(/vensim/i); + } + }); + + it('should load MDL file when Vensim support is available', async () => { + const supported = await Project.hasVensimSupport(); + + if (supported) { + // When Vensim support is available, openVensim should work + const mdlData = loadTestMdl(); + const project = await Project.openVensim(mdlData); + + expect(project).toBeInstanceOf(Project); + expect(project.modelCount).toBeGreaterThan(0); + + // The teacup model should have the expected variables + const model = project.mainModel; + const varNames = model.variables.map((v) => v.name.toLowerCase()); + expect(varNames).toContain('teacup temperature'); + + project.dispose(); + } + }); + + it('should accept MDL data as string when Vensim support is available', async () => { + const supported = await Project.hasVensimSupport(); + + if (supported) { + // openVensim should accept string data (like XMILE) + const mdlData = loadTestMdl(); + const mdlString = new TextDecoder().decode(mdlData); + const project = await Project.openVensim(mdlString); + + expect(project).toBeInstanceOf(Project); + project.dispose(); + } + }); + + it('should simulate models loaded from MDL when Vensim support is available', async () => { + const supported = await Project.hasVensimSupport(); + + if (supported) { + const mdlData = loadTestMdl(); + const project = await Project.openVensim(mdlData); + const model = project.mainModel; + + // Run simulation + const run = model.run(); + expect(run).toBeInstanceOf(Run); + + // Get results + const results = run.results; + expect(results.size).toBeGreaterThan(0); + + // Check that teacup temperature series exists and has expected behavior + // (temperature should decrease over time as teacup cools) + const tempSeries = results.get('teacup_temperature'); + if (tempSeries && tempSeries.length > 1) { + expect(tempSeries[0]).toBeGreaterThan(tempSeries[tempSeries.length - 1]); + } + + project.dispose(); + } + }); + }); });