const fs = require('node:fs');
|
const path = require('node:path');
|
const ts = require('typescript');
|
const assert = require('node:assert/strict');
|
const { parse, compileTemplate } = require('vue/compiler-sfc');
|
const root = path.join(__dirname, '../src/views/dashboard/analytics');
|
function evaluate(source, mocks) {
|
const js = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }).outputText;
|
const module = { exports: {} };
|
new Function('require', 'module', 'exports', 'defineOptions', 'defineProps', 'defineEmits', js)(
|
(id) => mocks[id] || {}, module, module.exports, () => {}, () => ({ repairActiveCount: 0 }), () => () => {});
|
return module.exports;
|
}
|
const vue = { computed: (getter) => ({ get value() { return getter(); } }), ref: (value) => ({ value }) };
|
async function scenario(codes, names, expected) {
|
const store = { accessRoutes: [{ name: 'Parent', children: names.map((name) => ({ name })) }] };
|
const home = evaluate(fs.readFileSync(path.join(root, 'use-home-access.ts'), 'utf8'), {
|
vue, '@vben/stores': { useAccessStore: () => store },
|
'@vben/access': { useAccess: () => ({ hasAccessByCodes: (requested) => requested.some((code) => codes.includes(code)) }) },
|
});
|
const calls = [];
|
const mocks = { '../use-home-access': home, './use-home-access': home,
|
'vue-router': { useRouter: () => ({ push: () => {} }) }, './data': { defaultSummary: {} } };
|
for (const [id, method] of [
|
['#/api/mes/home', 'getHomeSummary'], ['#/api/mes/home', 'getProductionTrend'],
|
['#/api/mes/home', 'getWorkOrderStatusDistribution'], ['#/api/erp/purchase/order', 'getPurchaseOrderPage'],
|
['#/api/erp/sale/order', 'getSaleOrderPage'], ['#/api/mes/wm/materialstock', 'getMaterialStockPage'],
|
['#/api/bpm/task', 'getTaskTodoPage'],
|
]) {
|
mocks[id] ||= {};
|
mocks[id][method] = async () => { calls.push(method); return method.startsWith('getHome') ? {} : method.endsWith('Page') ? { total: 3 } : []; };
|
}
|
mocks['@vben/plugins/echarts'] = { useEcharts: () => ({ renderEcharts: () => {} }) };
|
for (const file of ['index.vue', 'modules/biz-cards.vue', 'modules/production-trend.vue', 'modules/alert-panel.vue']) {
|
const mounted = [];
|
mocks.vue = { ...vue, onMounted: (fn) => mounted.push(fn) };
|
const { descriptor } = parse(fs.readFileSync(path.join(root, file), 'utf8'));
|
evaluate(descriptor.scriptSetup.content, mocks);
|
mounted.forEach((fn) => fn());
|
}
|
await new Promise((resolve) => setImmediate(resolve));
|
assert.deepEqual(calls.sort(), expected.sort());
|
const access = home.useHomeAccess();
|
store.accessRoutes = [{ name: 'Forbidden', meta: { menuVisibleWithForbidden: true }, children: [{ name: 'Child' }] }];
|
assert.equal(access.canNavigate('Forbidden'), false);
|
assert.equal(access.canNavigate('Child'), false);
|
}
|
(async () => {
|
await scenario([], [], []);
|
await scenario(['erp:purchase-order:query'], ['ErpPurchaseOrder'], ['getPurchaseOrderPage']);
|
await scenario(['erp:purchase-order:query'], [], []);
|
await scenario(['mes:home:query'], [], ['getHomeSummary', 'getProductionTrend', 'getWorkOrderStatusDistribution']);
|
await scenario(['*:*:*'], ['ErpPurchaseOrder', 'ErpSaleOrder', 'MesWmMaterialStock', 'BpmTaskTodo'],
|
['getHomeSummary', 'getProductionTrend', 'getWorkOrderStatusDistribution', 'getPurchaseOrderPage', 'getSaleOrderPage', 'getMaterialStockPage', 'getTaskTodoPage']);
|
for (const file of ['index.vue', ...fs.readdirSync(path.join(root, 'modules')).filter((f) => f.endsWith('.vue')).map((f) => `modules/${f}`)]) {
|
const { descriptor, errors } = parse(fs.readFileSync(path.join(root, file), 'utf8'));
|
assert.equal(errors.length, 0, file);
|
const result = compileTemplate({ source: descriptor.template.content, filename: file, id: file });
|
assert.equal(result.errors.length, 0, `${file}: ${result.errors}`);
|
}
|
console.log('PASS: 5 permission scenarios, forbidden routes, and all analytics Vue templates');
|
})();
|