import fs from 'node:fs'; import { createRequire } from 'node:module'; import { compileFunction } from 'node:vm'; import ts from 'typescript'; const require = createRequire(import.meta.url); export function props(patch = {}) { return { opportunity: { id: 1, title: 'SYNTHETIC resource investment', status: 'draft', deleted_at: null, }, etag: 'a'.repeat(64), resources: { documents: [], images: [], locations: [] }, existingLocations: [], assessment: [], imageProcessingAvailable: true, ...patch, }; } export function nodes(tree) { return Array.isArray(tree) ? tree.flatMap(nodes) : tree && typeof tree === 'object' ? [tree, ...nodes(tree.props?.children)] : []; } export function text(tree) { return Array.isArray(tree) ? tree.map(text).join('') : tree?.props ? text(tree.props.children) : typeof tree === 'string' || typeof tree === 'number' ? String(tree) : ''; } export function harness(initial = props()) { const frames = new Map(), sent = [], reloads = [], unload = new Set(); let active, cursor = 0, changed = false, effects = [], current = initial, tree, sequence = 0; const windowMock = { confirm() { return false; }, addEventListener(name, callback) { if (name === 'beforeunload') unload.add(callback); }, removeEventListener(name, callback) { if (name === 'beforeunload') unload.delete(callback); }, }; const state = (initialValue) => { const frame = active, i = cursor++; if (!(i in frame.cells)) frame.cells[i] = typeof initialValue === 'function' ? initialValue() : initialValue; return [ frame.cells[i], (value) => { const next = typeof value === 'function' ? value(frame.cells[i]) : value; if (!Object.is(next, frame.cells[i])) { frame.cells[i] = next; changed = true; } }, ]; }; const hooks = { useState: state, useRef(value) { const i = cursor++; active.cells[i] ??= { current: value }; return active.cells[i]; }, useEffect(callback, deps) { const frame = active, i = cursor++, prior = frame.cells[i]; if (!prior || !deps.every((value, j) => value === prior.deps[j])) { frame.cells[i] = { deps }; effects.push(() => { prior?.cleanup?.(); frame.cells[i].cleanup = callback(); }); } }, useCallback(callback, deps) { const i = cursor++, prior = active.cells[i]; if (!prior || !deps.every((value, j) => value === prior.deps[j])) active.cells[i] = { deps, callback }; return active.cells[i].callback; }, }; class Form { constructor() { this.values = new Map(); } set(k, v) { this.values.set(k, v); } get(k) { return this.values.get(k); } } function globals() { const saved = { document: globalThis.document, fetch: globalThis.fetch, FormData: globalThis.FormData, setTimeout: globalThis.setTimeout, }; globalThis.document = { cookie: 'XSRF-TOKEN=SYNTHETIC', getElementById: () => ({ focus() {} }), }; globalThis.FormData = Form; globalThis.setTimeout = (fn) => { fn(); return 0; }; globalThis.fetch = (url, options) => new Promise((resolve, reject) => sent.push({ url, form: options.body, resolve, reject }), ); return () => Object.assign(globalThis, saved); } const mocks = { react: hooks, '@inertiajs/react': { Link: 'a', usePage: () => ({ props: { basePath: '/minhas-oportunidades', countries: [], readOnly: false, }, url: '/minhas-oportunidades/1/resources', }), router: { reload: (o) => reloads.push(o) }, }, '@/components/opportunity-library-reuse': { default: () => null }, '@/components/territory-geometry-preview': { default: () => null }, '@/components/investment-territory': { TerritoryLevel: () => null }, './shared': { Management: 'main', Field: ({ label, children }) => ({ type: 'label', props: { children: [label, children] }, }), button: '', control: '', }, '@/lib/operation-key': { createOperationKey: () => `00000000-0000-4000-8000-${String(++sequence).padStart(12, '0')}`, }, }; function load(path) { const code = ts.transpileModule( fs.readFileSync( new URL('../../../' + path, import.meta.url), 'utf8', ), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, jsx: ts.JsxEmit.ReactJSX, }, }, ).outputText; const module = { exports: {} }; compileFunction(code, ['require', 'module', 'exports'])( (name) => mocks[name] ?? require(name), module, module.exports, ); return module.exports; } mocks['@/lib/technical-incident-labels'] = load( 'resources/js/lib/technical-incident-labels.ts', ); mocks['@/lib/value-text'] = load('resources/js/lib/value-text.ts'); mocks['@/components/opportunity-technical-incidents'] = load( 'resources/js/components/opportunity-technical-incidents.tsx', ); const Page = load( 'resources/js/pages/management/opportunity-resources.tsx', ).default; function expand(value, path, seen) { if (Array.isArray(value)) return value.map((child, i) => expand(child, path + '/' + (child?.key ?? i), seen), ); if (!value || typeof value !== 'object' || !value.props) return value; if (typeof value.type === 'function') { const id = path + '/' + value.type.name + ':' + (value.key ?? ''); seen.add(id); const frame = frames.get(id) ?? { cells: [] }; frames.set(id, frame); const previous = active, priorCursor = cursor; active = frame; cursor = 0; const rendered = value.type(value.props); active = previous; cursor = priorCursor; return { type: 'component', props: { name: value.type.name, children: expand(rendered, id, seen), }, }; } return { ...value, props: { ...value.props, children: expand( value.props.children, path + '/children', seen, ), }, }; } function render(next = current) { current = next; const previousWindow = globalThis.window; globalThis.window = windowMock; try { let loops = 0; do { changed = false; const seen = new Set(); tree = expand( { type: Page, key: 'page', props: current }, 'root', seen, ); for (const [id, frame] of frames) if (!seen.has(id)) { frame.cells.forEach((cell) => cell?.cleanup?.()); frames.delete(id); } const queue = effects; effects = []; queue.forEach((effect) => effect()); if (++loops > 12) throw Error('Component state did not settle'); } while (changed); return tree; } finally { globalThis.window = previousWindow; } } render(); const field = (label) => nodes(tree).find( (n) => ['input', 'textarea', 'select'].includes(n.type) && n.props['aria-label'] === label, ); return { sent, reloads, render, get tree() { return tree; }, get props() { return current; }, value(label) { return field(label)?.props.value; }, change(label, value) { field(label).props.onChange({ target: { value } }); render(); }, choose(label) { nodes(tree) .find((n) => n.type === 'button' && text(n) === label) .props.onClick(); render(); }, attach(value) { nodes(tree) .find((n) => n.type === 'input' && n.props.type === 'file') .props.onChange({ target: { files: [value] } }); render(); }, submit() { const restore = globals(); try { nodes(tree) .find((n) => n.type === 'form') .props.onSubmit({ preventDefault() {} }); render(); return sent.at(-1); } finally { restore(); } }, async reply(status, data, request = sent.at(-1)) { const restore = globals(); try { request.resolve({ ok: status >= 200 && status < 300, status, json: async () => data, }); for (let i = 0; i < 8; i++) await Promise.resolve(); render(); } finally { restore(); } }, }; }