Files
Investidor-V2/tests/frontend/helpers/profile-support-installation.mjs
T

379 lines
12 KiB
JavaScript

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 const apply = 'Aplicar actualiza\u00e7\u00e3o do Suporte t\u00e9cnico';
export const refresh = 'Actualizar pr\u00e9via';
export const adopt = 'Confirmar leitura da pr\u00e9via actual';
export const retry = 'Repetir envio original';
export const reason = 'Actualizacao sintetica explicitamente fundamentada';
export function props(patch = {}) {
return {
actorId: 1,
target: { database: 'support_profile_installation_test', port: 55441 },
hash: 'a'.repeat(64),
code_hash: 'b'.repeat(64),
installed: false,
consistent: true,
repairable: false,
ready: true,
action: 'install',
schema: { state: 'base', ledger: false },
dependencies: { profiles_installed: true },
changes: [],
backupDate: '2026-11-03T00:00:00Z',
rollback: 'Conservar historico',
commands: [],
mfa_confirmed: true,
...patch,
};
}
export function result(request, patch = {}) {
return {
actor_id: 1,
command_key: request.data.idempotency_key,
target: props().target,
migration: '2026_11_03_080000_add_technical_support_profile',
code_hash: props().code_hash,
installed: true,
...patch,
};
}
export function committed(request, patch = {}) {
return props({
installed: true,
action: 'none',
hash: 'c'.repeat(64),
schema: { state: 'support', ledger: true },
commands: [
{ key: request.data.idempotency_key, code_hash: props().code_hash },
],
...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(), mode = 'support') {
const frames = new Map(),
sent = [],
reloads = [],
before = new Set(),
unload = new Set();
let active,
cursor = 0,
changed = false,
effects = [],
current = initial,
tree,
sequence = 0,
confirmCalls = 0;
const windowMock = {
confirm() {
confirmCalls++;
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 ApiError extends Error {
constructor(status, errors = {}) {
super('SYNTHETIC HTTP ' + status);
this.status = status;
this.errors = errors;
}
}
const mocks = {
react: hooks,
'@inertiajs/react': {
Link: 'a',
router: {
reload: (options) => reloads.push(options),
on(name, fn) {
if (name === 'before') before.add(fn);
return () => before.delete(fn);
},
},
},
'../management/shared': { Management: 'main', button: '', control: '' },
'@/pages/management/shared': {
Management: 'main',
button: '',
control: '',
},
};
const request = (url, options = {}) =>
new Promise((resolve, reject) => {
const entry = {
url,
data: options.body ? JSON.parse(options.body) : null,
resolve,
reject,
};
(options.method === 'POST' ? sent : reloads).push(entry);
});
mocks['./portal-api'] = {
PortalApiError: ApiError,
portalRequest: request,
};
mocks['@/lib/profile-json'] = {
profileJson: (url, data, method = 'POST') =>
request(url, { method, body: JSON.stringify(data) }),
};
mocks['./operation-key'] = mocks['@/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['./page-preparation-guard'] = mocks['@/lib/page-preparation-guard'] =
load('resources/js/lib/page-preparation-guard.ts');
mocks['@/lib/profile-support-installation'] = load(
'resources/js/lib/profile-support-installation.ts',
);
const Page = load(
'resources/js/pages/profiles/' +
(mode === 'support' ? 'support-installation' : 'installation') +
'.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();
return {
sent,
reloads,
render,
get props() {
return current;
},
get tree() {
return tree;
},
get confirmCalls() {
return confirmCalls;
},
get reason() {
return nodes(tree).find((n) => n.type === 'textarea').props.value;
},
button(label) {
return nodes(tree).find(
(n) => n.type === 'button' && text(n) === label,
);
},
change(value) {
nodes(tree)
.find((n) => n.type === 'textarea')
.props.onChange({ target: { value } });
render();
},
confirm(value = true) {
nodes(tree)
.find((n) => n.type === 'input' && n.props.type === 'checkbox')
.props.onChange({ target: { checked: value } });
render();
},
number(index, value) {
nodes(tree)
.filter((n) => n.type === 'input' && n.props.type === 'number')
[index].props.onChange({ target: { value } });
render();
},
get guards() {
return before.size;
},
click(label) {
const node = this.button(label);
if (!node) throw Error('Missing button ' + label);
node.props.onClick();
render();
return sent.at(-1);
},
async reply(value, request = sent.at(-1)) {
request.resolve(value);
for (let i = 0; i < 6; i++) await Promise.resolve();
render();
},
async fail(status = null, request = sent.at(-1)) {
request.reject(
status === null
? new Error('SYNTHETIC network loss')
: new ApiError(
status,
status === 422
? { note: ['SYNTHETIC rejected note'] }
: {},
),
);
for (let i = 0; i < 6; i++) await Promise.resolve();
render();
},
leave(preserveState = false) {
let prevented = false;
const old = globalThis.window;
globalThis.window = windowMock;
try {
for (const fn of before)
fn({
detail: { visit: { method: 'get', preserveState } },
preventDefault() {
prevented = true;
},
});
} finally {
globalThis.window = old;
}
return prevented;
},
};
}