Files
otaman/frontend/tests/custom_inject.test.js
T
catarrh b0b8d02fe7 custom files: any DF parent path at any depth (v2.2.11)
The parent field was a select over custom DFs only, so a standard DF (e.g.
MF/7F20) could not be a parent and canonical entries under one were dropped
on import. It is now a free-text path with suggestions from the root, custom
DFs and DFs of the loaded tree (any depth); a bare FID chain is completed
from the root selector. Validation accepts standard or not-yet-seen parents
(flagged in the list), rejects a parent known to be an EF, and import keeps
such entries. Custom-file edits re-inject the loaded tree: new nodes appear
at once, renamed model nodes are restored and injected nodes removed when
their entry goes away. Help updated (the form lives in Profiler, not Card
reader). SW cache otaman-v176.
2026-09-16 23:47:30 +03:00

126 lines
4.7 KiB
JavaScript

const { test } = require('node:test');
const assert = require('node:assert');
const fs = require('node:fs');
const path = require('node:path');
const html = fs.readFileSync(path.join(__dirname, '..', 'index.html'), 'utf8');
function extractFunc(src, name) {
const re = new RegExp('function\\s+' + name + '\\s*\\([^)]*\\)\\s*\\{');
const m = re.exec(src);
if (!m) throw new Error('function ' + name + ' not found');
let i = m.index + m[0].length - 1;
let depth = 0;
for (; i < src.length; i++) {
if (src[i] === '{') depth++;
else if (src[i] === '}') {
depth--;
if (depth === 0) break;
}
}
return src.slice(m.index, i + 1);
}
let code = 'var pysimCustomFiles = [];\nvar pysimFsTreeRoot = null;\n';
for (const fn of ['pysimFsNodePath', 'pysimCustomInject', 'pysimCustomRefreshTree', 'pysimCustomParent', 'pysimCustomFid']) {
code += extractFunc(html, fn) + '\n';
}
code += 'globalThis.pysimFsRenderTree = () => {};\n';
eval(code);
function node(name, fid, parent, children) {
return { name, fid, parent: parent || null, children: children || null, isDir: true };
}
function tree() {
const mf = node('MF', '3F00', null, []);
const dfA = node('DF.A', '5F01', mf);
const dfB = node('DF.B', '5F02', mf);
const dfC = node('DF.C', '5F03', mf, [node('EF.X', '6F46', null)]);
dfC.children[0].parent = dfC;
const adf = node('ADF.USIM', '7FFF', mf);
const efImsi = node('EF.IMSI', '6F07', adf);
adf.children = [efImsi];
mf.children = [dfA, dfB, dfC, adf];
return { mf, dfA, dfB, dfC, adf, efImsi };
}
test('pysimFsNodePath builds canonical paths from MF and ADF roots', () => {
const t = tree();
assert.strictEqual(pysimFsNodePath(t.mf), 'MF');
assert.strictEqual(pysimFsNodePath(t.dfA), 'MF/5F01');
assert.strictEqual(pysimFsNodePath(t.dfC.children[0]), 'MF/5F03/6F46');
assert.strictEqual(pysimFsNodePath(t.adf), 'ADF.USIM');
assert.strictEqual(pysimFsNodePath(t.efImsi), 'ADF.USIM/6F07');
});
test('injection matches full paths, so same-FID DFs stay apart', () => {
const t = tree();
pysimCustomFiles = [
{ path: 'MF/5F01/6F46', name: 'EF.ONLY-A', kind: 'ef' },
{ path: 'MF/5F02/6F46', name: 'EF.ONLY-B', kind: 'ef' },
{ path: 'ADF.USIM/6F07', name: 'EF.MY-IMSI', kind: 'ef' },
];
pysimCustomInject(t.mf);
assert.strictEqual(t.mf.children.length, 4, 'MF-level injection adds nothing');
pysimCustomInject(t.dfA);
assert.strictEqual(t.dfA.children.length, 1);
assert.strictEqual(t.dfA.children[0].name, 'EF.ONLY-A');
assert.strictEqual(t.dfA.children[0].customPath, 'MF/5F01/6F46');
pysimCustomInject(t.dfB);
assert.strictEqual(t.dfB.children[0].name, 'EF.ONLY-B');
pysimCustomInject(t.dfC);
assert.strictEqual(t.dfC.children.length, 1, 'unrelated DF is untouched');
pysimCustomInject(t.adf);
assert.strictEqual(t.adf.children.length, 1);
assert.strictEqual(t.adf.children[0].name, 'EF.MY-IMSI');
assert.strictEqual(t.adf.children[0].customPath, 'ADF.USIM/6F07');
});
test('injection renames and marks an existing model node', () => {
const t = tree();
pysimCustomFiles = [{ path: 'MF/5F03/6F46', name: 'EF.RENAMED', kind: 'ef' }];
pysimCustomInject(t.dfC);
assert.strictEqual(t.dfC.children.length, 1);
assert.strictEqual(t.dfC.children[0].name, 'EF.RENAMED');
assert.strictEqual(t.dfC.children[0].custom, true);
});
test('injected DFs are directories and can host their own children', () => {
const t = tree();
pysimCustomFiles = [
{ path: 'MF/5F10', name: 'DF.NEW', kind: 'df' },
{ path: 'MF/5F10/6F46', name: 'EF.UNDER-NEW', kind: 'ef' },
];
pysimCustomInject(t.mf);
const df = t.mf.children.find(c => c.fid === '5F10');
assert.ok(df, 'custom DF injected into MF');
assert.strictEqual(df.isDir, true);
assert.strictEqual(pysimFsNodePath(df), 'MF/5F10');
pysimCustomInject(df);
assert.strictEqual(df.children.length, 1);
assert.strictEqual(df.children[0].name, 'EF.UNDER-NEW');
});
test('refresh restores renamed model nodes and drops injected ones', () => {
const t = tree();
pysimFsTreeRoot = t.mf;
pysimCustomFiles = [{ path: 'MF/5F03/6F46', name: 'EF.RENAMED', kind: 'ef' }];
pysimCustomRefreshTree();
assert.strictEqual(t.dfC.children[0].name, 'EF.RENAMED');
assert.strictEqual(t.dfC.children[0].custom, true);
// deleting the entry brings the model name back
pysimCustomFiles = [];
pysimCustomRefreshTree();
assert.strictEqual(t.dfC.children[0].name, 'EF.X');
assert.ok(!t.dfC.children[0].custom);
assert.strictEqual(t.dfC.children[0].modelName, undefined);
// injected nodes disappear together with their entry
pysimCustomFiles = [{ path: 'MF/5F10', name: 'DF.NEW', kind: 'df' }];
pysimCustomRefreshTree();
assert.ok(t.mf.children.some(c => c.fid === '5F10'));
pysimCustomFiles = [];
pysimCustomRefreshTree();
assert.ok(!t.mf.children.some(c => c.fid === '5F10'));
});