import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import { compileFunction } from 'node:vm'; import { createRequire } from 'node:module'; import React from 'react'; import ts from 'typescript'; import * as finance from '../../resources/js/lib/financial-calculators.ts'; import * as contract from '../../resources/js/lib/simulation-contract.ts'; import * as fundingContext from '../../resources/js/lib/planning-finance-context.ts'; const require = createRequire(import.meta.url); const nodes = tree => Array.isArray(tree) ? tree.flatMap(nodes) : tree && typeof tree === 'object' ? [tree, ...nodes(tree.props?.children)] : []; const text = tree => Array.isArray(tree) ? tree.map(text).join('') : tree?.props ? text(tree.props.children) : typeof tree === 'string' ? tree : ''; function harness(snapshot = '') { let cursor=0, queue=[], dirty=false, tree; const memory=[], deps=[]; const hooks={...React, useState(initial) {const id=cursor++;if(!(id in memory))memory[id]=typeof initial==='function'?initial():initial;return[memory[id],next=>{const value=typeof next==='function'?next(memory[id]):next;if(!Object.is(value,memory[id])){memory[id]=value;dirty=true;}}];},useEffect(effect, next) {const id=cursor++;if(!deps[id]||next.some((value,i)=>!Object.is(value,deps[id][i]))){queue.push(effect);deps[id]=next;}}}; const module={exports:{}}; compileFunction(ts.transpileModule(fs.readFileSync('resources/js/components/simulation-financial-tools.tsx','utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,jsx:ts.JsxEmit.ReactJSX}}).outputText,['require','module','exports'])(id=>id==='react'?hooks:id==='@/components/currency-select'?{default:()=>null}:id==='@/lib/financial-calculators'?finance:id==='@/lib/simulation-contract'?contract:id==='@/lib/planning-finance-context'?fundingContext:require(id),module,module.exports); const props={currency:'MZN',focus:'funding',loanRelevant:false,snapshot,sourceFunding:{total:'2000000',secured:''},onSnapshotChange(value){props.snapshot=value;dirty=true;},onCurrencyChange(){},onFundingChange(key,value){props.sourceFunding={...props.sourceFunding,[key]:value};dirty=true;}}; const render=()=>{for(let i=0;i<12;i++){cursor=0;queue=[];dirty=false;tree=module.exports.SimulationFinancialTools(props);for(const effect of queue)effect();if(!dirty)return;}throw new Error('Render did not settle');}; render(); return{props,render,text:()=>text(tree),field:label=>nodes(tree).find(node=>node.props?.label===label),button:label=>nodes(tree).find(node=>node.type==='button'&&text(node).startsWith(label))}; } test('editing canonical mobilised funding updates the attached calculator and snapshot without showing hidden-tool errors',()=>{ const h=harness(); h.props.sourceFunding={total:'2000000',secured:'1000000'};h.render(); assert.equal(h.field('Montante já mobilizado').props.value,'1000000'); assert.match(h.text(),/1000000.00/); assert.equal(finance.restoreFinancialToolsSnapshot(h.props.snapshot).funding.secured,'1000000'); assert.doesNotMatch(h.text(),/Empréstimo:|Ponto de equilíbrio:|Orçamento:|valores da calculadora diferem/); assert.equal(h.button('Exportar resultados'),undefined); }); test('a known different recovered calculator value is preserved until the user resolves its source',()=>{ const snapshot=finance.serializeFinancialToolsSnapshot({budgetPeriod:'Mensal',loan:{principal:'',rate:'',months:'',rateType:'nominal',periodMonths:'1',gracePeriods:'',upfrontFee:'',recurringFee:''},breakEven:{fixed:'',price:'',variable:''},funding:{total:'2000000',secured:'250000'},incomes:[{label:'Receitas',amount:''}],expenses:[{label:'Custos',amount:''}]}); const h=harness(snapshot);h.props.sourceFunding={total:'2000000',secured:'1000000'};h.render(); assert.equal(h.field('Montante já mobilizado').props.value,'250000'); assert.match(h.text(),/valores da calculadora diferem/); h.button('Usar os valores do investimento').props.onClick();h.render(); assert.equal(h.field('Montante já mobilizado').props.value,'1000000'); h.props.sourceFunding={total:'2000000',secured:'1500000'};h.render(); assert.equal(h.field('Montante já mobilizado').props.value,'1500000'); assert.match(h.text(),/500000.00/); });