const assert = require('node:assert/strict'); const { readFileSync } = require('node:fs'); const { test } = require('node:test'); const { Module, createRequire } = require('node:module'); const { resolve } = require('node:path'); const ts = require('typescript'); const requireWeb = createRequire(resolve(__dirname, '../package.json')); const file = resolve(__dirname, '../src/pages/accounts/$id/edit.tsx'); function setup({ environment = { alias: 'env-a', memory_limit_mb: 8192, runtime_id: 'running', fingerprint: { seed: 42 }, profile_id: 'existing', gateway: 'gw-a' }, saveError } = {}) { const states = [], forms = [], effects = [], calls = [], messages = []; let cursor = 0, formCursor = 0; const marker = name => Object.assign(() => null, { displayName: name }); const Form = Object.assign(marker('Form'), { Item: marker('Form.Item'), useForm() { const index = formCursor++; forms[index] ||= { values: {}, setFieldsValue(values) { Object.assign(this.values, values); }, getFieldValue(name) { return this.values[name]; }, async validateFields() { return this.values; } }; return [forms[index]]; }, }); const antd = { Form, App: { useApp: () => ({ message: { success: text => messages.push(text), error: text => messages.push(text) } }) } }; for (const name of ['Alert', 'Button', 'Card', 'Col', 'Flex', 'Row', 'Select', 'Space', 'InputNumber']) antd[name] = marker(name); antd.Input = Object.assign(marker('Input'), { Password: marker('Password'), TextArea: marker('TextArea') }); antd.Descriptions = Object.assign(marker('Descriptions'), { Item: marker('Descriptions.Item') }); antd.Typography = { Title: marker('Title'), Text: marker('Text'), Paragraph: marker('Paragraph') }; const loaded = new Module(file, module); loaded.require = id => { if (id === 'react') return { useState(value) { const index = cursor++; if (!(index in states)) states[index] = value; return [states[index], next => { states[index] = typeof next === 'function' ? next(states[index]) : next; }]; }, useCallback: callback => callback, useEffect(effect) { if (!effects.length) effects.push(effect); }, }; if (id === 'antd') return antd; if (id === '@umijs/max') return { useParams: () => ({ id: 'account-a' }), history: { push() {} } }; if (id === '@/components/PageActions') return { usePageActions() {}, usePageInfo() {} }; if (id === '@/services/api') return { async getList() { return { data: [{ id: 'account-a', platform: 'douyin' }] }; }, async getOne() { return { environment }; }, async creatorUpdate(path, data) { calls.push({ path, data }); if (saveError) throw saveError; return { ...environment, ...data }; }, }; if (id === '@/utils/helpers') return { conflictMessage: (error, fallback) => error.message || fallback, dateTime: String }; if (id === '@/components/accounts/FingerprintFields') return { default: marker('FingerprintFields'), fingerprintFormValues: fingerprint => ({ fingerprint }), fingerprintPayload: value => value }; if (id.startsWith('@/components/')) return { default: marker(id) }; return requireWeb(id); }; loaded._compile(ts.transpileModule(readFileSync(file, 'utf8'), { fileName: file, compilerOptions: { module: ts.ModuleKind.CommonJS, jsx: ts.JsxEmit.ReactJSX, inlineSourceMap: true, inlineSources: true } }).outputText, file); let tree; function render() { cursor = 0; formCursor = 0; tree = loaded.exports.default(); return tree; } function all(name, node = tree) { if (!node || typeof node !== 'object') return []; return [...(node.type?.displayName === name ? [node] : []), ...[node.props?.children].flat(Infinity).flatMap(child => child == null ? [] : all(name, child))]; } render(); return { forms, calls, messages, render, all, async load() { effects[0](); for (let i = 0; i < 8; i++) await Promise.resolve(); render(); } }; } test('编辑回填真实内存上限,运行中独立保存只发内存字段,不触发stop/start或指纹保存', async () => { const s = setup(); await s.load(); const field = s.all('Form.Item').find(node => node.props.name === 'memory_limit_mb'); assert.ok(field, '编辑页必须提供独立内存设置'); const memoryForm = s.forms.find(form => Object.hasOwn(form.values, 'memory_limit_mb')); assert.equal(memoryForm.values.memory_limit_mb, 8192); assert.equal(field.props.initialValue, undefined); assert.equal(s.all('InputNumber')[0].props.disabled, undefined); const form = s.all('Form').find(node => node.props.form === memoryForm); assert.equal(form.props.disabled, false, '运行中也允许保存内存设置'); memoryForm.values.memory_limit_mb = 4096; const saving = form.props.onFinish(memoryForm.values); s.render(); assert.equal(s.all('Form').find(node => node.props.form === memoryForm).props.disabled, true); await saving; assert.deepEqual(s.calls, [{ path: '/creator/accounts/account-a/fingerprint', data: { memory_limit_mb: 4096 } }]); assert.match(s.messages.at(-1), /下次启动.*生效/); }); test('编辑缺失内存字段不伪造2048;原生规则拒绝空值、越界与小数', async () => { const s = setup({ environment: { alias: 'env-a' } }); await s.load(); const field = s.all('Form.Item').find(node => node.props.name === 'memory_limit_mb'); assert.ok(field); const memoryForm = s.forms.find(form => Object.hasOwn(form.values, 'memory_limit_mb')); assert.equal(memoryForm.values.memory_limit_mb, undefined); const Schema = requireWeb('@rc-component/async-validator').default; const schema = new Schema({ memory_limit_mb: field.props.rules }); for (const value of [512, 65536]) await schema.validate({ memory_limit_mb: value }); for (const value of [undefined, null, 511, 65537, 512.5]) await assert.rejects(schema.validate({ memory_limit_mb: value })); }); test('未绑定环境不允许保存内存上限,也不创建环境', async () => { const s = setup({ environment: null }); await s.load(); const memoryForm = s.forms.find(form => Object.hasOwn(form.values, 'memory_limit_mb')); const form = s.all('Form').find(node => node.props.form === memoryForm); assert.equal(form.props.disabled, true); await form.props.onFinish({ memory_limit_mb: 4096 }); assert.deepEqual(s.calls, []); }); test('内存保存失败明确显示原因,不提示成功', async () => { const s = setup({ saveError: new Error('环境不可用') }); await s.load(); const memoryForm = s.forms.find(form => Object.hasOwn(form.values, 'memory_limit_mb')); assert.ok(memoryForm); await s.all('Form').find(node => node.props.form === memoryForm).props.onFinish({ memory_limit_mb: 4096 }); assert.deepEqual(s.messages, ['环境不可用']); });