test scripts: known-file picker for the file actions

The UPDATE file / READ file step editor's path field gains the selector
the APDU builder's SELECT rows use: the loaded file-manager tree, the
custom files and the standard uicc_files.json list, merged by
rfmFileEntries, grouped by root with a filter.  Picking a file fills
test-f-path; the text field stays authoritative (a name path or an
unlisted file can still be typed) and the current path is preselected
when known.  Only EFs are offered - the action reads/writes a file, so a
DF/ADF is not a target.

v3.38.0: server.py VERSION, pyproject.toml and the PWA header;
sw cache simple-v478 (help/index changed); a CHANGELOG [3.38.0] section,
the help EN/RU, docs/test-scripts.md and the AGENTS notes.

Tests: frontend/tests/testscripts.test.js - the option list/grouping, the
EF-only filter, the filter input, the pick->path fill and the form's
picker render for write and read.  1061 Python + 1071 frontend.
This commit is contained in:
catarrh committed 2026-10-24 10:38:08 +03:00
1 parent 513f16d9d2
commit cd0db3f9d3
9 files changed
+145 -7

No files matched your search

+58 -1
View File
@@ -1959,7 +1959,7 @@
// ===== Version =====
// Single source of truth for the PWA version: shown in the header and used
// by the server version check in pysimConnect().
const SIMPLE_VERSION = '3.37.0';
const SIMPLE_VERSION = '3.38.0';
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
// ===== view history (the browser Back/Forward buttons) =====
@@ -8344,6 +8344,53 @@ function testFormSelect(id, value, options, onchange) {
return html + '</select>';
}
// The file actions' path picker: the known EFs (the loaded file-manager
// tree, the custom files and the standard file list, merged by
// rfmFileEntries) as full canonical paths, grouped by root. The path field
// stays authoritative - a pick only fills it (a name path or an unlisted
// file stays possible).
function testPathEntries() {
return rfmFileEntries().filter(e => e.kind === 'ef');
}
function testPathOptionsHtml(filter, current) {
const f = String(filter || '').toUpperCase();
const cur = String(current || '').toUpperCase();
const groups = new Map();
for (const e of testPathEntries()) {
if (f && (e.name + ' ' + e.path).toUpperCase().indexOf(f) < 0) continue;
if (!groups.has(e.root)) groups.set(e.root, []);
groups.get(e.root).push(e);
}
let html = '<option value="">' + esc(t('— pick a known file —')) + '</option>';
for (const [root, items] of groups) {
html += '<optgroup label="' + esc(root) + '">';
for (const e of items) {
const inRoot = e.path.slice(root.length + 1);
html += '<option value="' + esc(e.path) + '"' +
(e.path === cur ? ' selected' : '') + '>' +
esc(e.name + ' — ' + inRoot) + '</option>';
}
html += '</optgroup>';
}
if (!groups.size) {
html += '<option value="" disabled>' + esc(t('no matching files')) + '</option>';
}
return html;
}
function testPathPick(sel) {
const input = document.getElementById('test-f-path');
if (input && sel && sel.value) input.value = sel.value;
}
function testPathFilterInput(input) {
const sel = document.getElementById('test-f-pathpick');
const path = document.getElementById('test-f-path');
if (sel) sel.innerHTML = testPathOptionsHtml(input ? input.value : '',
path ? path.value : '');
}
function testStepRender() {
const modal = document.getElementById('test-step-modal');
const title = document.getElementById('test-step-title');
@@ -8412,6 +8459,13 @@ function testStepRender() {
{v: 'true', l: t('Case-sensitive')}]));
} else if (step.kind === 'file-write' || step.kind === 'file-read') {
html += testFormRow('File path', testFormInput('test-f-path', p.path, 'ADF.USIM/EF.EST or MF/2FE2'));
html += testFormRow('\u2026 or pick a known file',
'<div class="flex gap-2">' +
'<input id="test-f-pathfilter" placeholder="filter" oninput="testPathFilterInput(this)" ' +
'class="font-mono text-xs border border-gray-300 dark:border-slate-600 rounded px-1.5 py-0.5 dark:bg-slate-800 w-40">' +
'<select id="test-f-pathpick" onchange="testPathPick(this)" ' +
'class="flex-1 border border-gray-300 dark:border-slate-600 text-xs rounded px-2 py-1.5 dark:bg-slate-800 font-mono">' +
testPathOptionsHtml('', p.path) + '</select></div>');
html += testFormRow('Mode', testFormSelect('test-f-mode', p.mode || 'auto', [
{v: 'auto', l: t('Auto (by file type)')}, {v: 'binary', l: t('Transparent (binary)')}, {v: 'record', l: t('Record')}]));
html += testFormRow('Record number (record mode)', testFormInput('test-f-record', p.record, '1..255'));
@@ -26584,6 +26638,9 @@ const LANG_RU = {
'item id or text is required': 'нужен item id или текст пункта',
'give the item id or the text, not both': 'укажите item id или текст, но не оба',
'… or item text': '… или текст пункта',
'… or pick a known file': '… или выберите известный файл',
'— pick a known file —': '— выберите известный файл —',
'no matching files': 'нет подходящих файлов',
'menu item text': 'текст пункта меню',
'Match case': 'Учёт регистра',
'Case-insensitive (default)': 'Без учёта регистра (по умолчанию)',