profiler: switch Profiles / Card snapshots with tabs instead of two columns
The list view now has a centered pill row (Profiles | Card snapshots) and shows one list at a time; the last active tab persists while the app is open. Snapshot flows (scan, import) switch to the snapshots tab, profile import switches to profiles. Help docs reworded (tabs). New DOM test for profilerListSwitch. SW cache v81 -> v82.
This commit is contained in:
@@ -386,7 +386,7 @@
|
|||||||
<p class="text-sm mb-2">Диалог сканирования запрашивает имя профиля и предлагает селектор <strong>«Проверка FCP/FCI»</strong> (те же три режима, по умолчанию <strong>Тип файла + размер (FCP)</strong>), применяемый ко всем создаваемым правилам, а также список <strong>«Игнорировать содержимое файлов»</strong> (все отмечены по умолчанию, кроме <code class="font-mono text-sm">EF.ARR</code>; флажок в заголовке отмечает или снимает весь список) часто перезаписываемых файлов, содержимое которых пропускается: <code class="font-mono text-sm">EF.LOCI</code>, <code class="font-mono text-sm">EF.PSLOCI</code>, <code class="font-mono text-sm">EF.EPSLOCI</code>, <code class="font-mono text-sm">EF.5GS3GPPLOCI</code>, <code class="font-mono text-sm">EF.Keys</code>, <code class="font-mono text-sm">EF.KeysPS</code>, <code class="font-mono text-sm">EF.SMS</code>, <code class="font-mono text-sm">EF.Kc</code>, <code class="font-mono text-sm">EF.KcGPRS</code>, <code class="font-mono text-sm">EF.LOCIGPRS</code>, <code class="font-mono text-sm">EF.CBMID</code>, <code class="font-mono text-sm">EF.SMSS</code>, <code class="font-mono text-sm">EF.ACC</code>, <code class="font-mono text-sm">EF.EPSNSC</code>, <code class="font-mono text-sm">EF.START-HFN</code>, <code class="font-mono text-sm">EF.ARR</code>. Ещё две отмеченные по умолчанию опции <strong>«Сравнивать первые 4 байта для»</strong> <code class="font-mono text-sm">EF.IMSI</code> и <code class="font-mono text-sm">EF.ICCID</code> захватывают содержимое этих файлов как маску только первых 4 байт (снимите для точного сравнения). Строка прогресса показывает <em>N / всего файлов</em> с текущим путём файла во время сканирования; при сканировании опции скрываются, а кнопки блокируются. Правила создаются только для файлов, которые реально существуют на карте (возвращён FCP-шаблон); отсутствующие файлы пропускаются. Пользовательские файлы из подвкладки <strong>«Пользовательские файлы»</strong> включаются с той же проверкой существования.</p>
|
<p class="text-sm mb-2">Диалог сканирования запрашивает имя профиля и предлагает селектор <strong>«Проверка FCP/FCI»</strong> (те же три режима, по умолчанию <strong>Тип файла + размер (FCP)</strong>), применяемый ко всем создаваемым правилам, а также список <strong>«Игнорировать содержимое файлов»</strong> (все отмечены по умолчанию, кроме <code class="font-mono text-sm">EF.ARR</code>; флажок в заголовке отмечает или снимает весь список) часто перезаписываемых файлов, содержимое которых пропускается: <code class="font-mono text-sm">EF.LOCI</code>, <code class="font-mono text-sm">EF.PSLOCI</code>, <code class="font-mono text-sm">EF.EPSLOCI</code>, <code class="font-mono text-sm">EF.5GS3GPPLOCI</code>, <code class="font-mono text-sm">EF.Keys</code>, <code class="font-mono text-sm">EF.KeysPS</code>, <code class="font-mono text-sm">EF.SMS</code>, <code class="font-mono text-sm">EF.Kc</code>, <code class="font-mono text-sm">EF.KcGPRS</code>, <code class="font-mono text-sm">EF.LOCIGPRS</code>, <code class="font-mono text-sm">EF.CBMID</code>, <code class="font-mono text-sm">EF.SMSS</code>, <code class="font-mono text-sm">EF.ACC</code>, <code class="font-mono text-sm">EF.EPSNSC</code>, <code class="font-mono text-sm">EF.START-HFN</code>, <code class="font-mono text-sm">EF.ARR</code>. Ещё две отмеченные по умолчанию опции <strong>«Сравнивать первые 4 байта для»</strong> <code class="font-mono text-sm">EF.IMSI</code> и <code class="font-mono text-sm">EF.ICCID</code> захватывают содержимое этих файлов как маску только первых 4 байт (снимите для точного сравнения). Строка прогресса показывает <em>N / всего файлов</em> с текущим путём файла во время сканирования; при сканировании опции скрываются, а кнопки блокируются. Правила создаются только для файлов, которые реально существуют на карте (возвращён FCP-шаблон); отсутствующие файлы пропускаются. Пользовательские файлы из подвкладки <strong>«Пользовательские файлы»</strong> включаются с той же проверкой существования.</p>
|
||||||
|
|
||||||
<h4 id="card-snapshots" class="font-medium mb-1">Снимки карт</h4>
|
<h4 id="card-snapshots" class="font-medium mb-1">Снимки карт</h4>
|
||||||
<p class="text-sm mb-2">Представление списка показывает две колонки — <strong>«Профили»</strong> и <strong>«Снимки карт»</strong>. Снимок карты — неизменяемая фиксация файловой системы карты: для каждого существующего файла сохраняются путь, символьное имя, тип, размер (или длина/число записей), сырой FCI из ответа SELECT и содержимое, если файл читается (без списка игнорирования и без масок). ICCID декодируется из EF.ICCID и показывается рядом с именем снимка.</p>
|
<p class="text-sm mb-2">Представление списка имеет две вкладки — <strong>«Профили»</strong> и <strong>«Снимки карт»</strong>. Снимок карты — неизменяемая фиксация файловой системы карты: для каждого существующего файла сохраняются путь, символьное имя, тип, размер (или длина/число записей), сырой FCI из ответа SELECT и содержимое, если файл читается (без списка игнорирования и без масок). ICCID декодируется из EF.ICCID и показывается рядом с именем снимка.</p>
|
||||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||||
<li><strong>Новый снимок</strong> — запрашивает имя и сканирует карту, затем возвращает к списку.</li>
|
<li><strong>Новый снимок</strong> — запрашивает имя и сканирует карту, затем возвращает к списку.</li>
|
||||||
<li><strong>Импорт снимка</strong> — загружает снимок из JSON-файла.</li>
|
<li><strong>Импорт снимка</strong> — загружает снимок из JSON-файла.</li>
|
||||||
|
|||||||
+1
-1
@@ -386,7 +386,7 @@
|
|||||||
<p class="text-sm mb-2">The scan dialog asks for a profile name and offers a <strong>“FCP/FCI check”</strong> selector (the same three modes above, default <strong>Filetype + size</strong>) applied to every generated rule, plus an <strong>“Ignore contents of files”</strong> checklist (all checked by default except <code class="font-mono text-sm">EF.ARR</code>; the header checkbox checks or unchecks the whole list) of frequently-overwritten files whose contents are skipped: <code class="font-mono text-sm">EF.LOCI</code>, <code class="font-mono text-sm">EF.PSLOCI</code>, <code class="font-mono text-sm">EF.EPSLOCI</code>, <code class="font-mono text-sm">EF.5GS3GPPLOCI</code>, <code class="font-mono text-sm">EF.Keys</code>, <code class="font-mono text-sm">EF.KeysPS</code>, <code class="font-mono text-sm">EF.SMS</code>, <code class="font-mono text-sm">EF.Kc</code>, <code class="font-mono text-sm">EF.KcGPRS</code>, <code class="font-mono text-sm">EF.LOCIGPRS</code>, <code class="font-mono text-sm">EF.CBMID</code>, <code class="font-mono text-sm">EF.SMSS</code>, <code class="font-mono text-sm">EF.ACC</code>, <code class="font-mono text-sm">EF.EPSNSC</code>, <code class="font-mono text-sm">EF.START-HFN</code>, <code class="font-mono text-sm">EF.ARR</code>. Two further checked-by-default options <strong>“Match first 4 bytes for”</strong> <code class="font-mono text-sm">EF.IMSI</code> and <code class="font-mono text-sm">EF.ICCID</code> capture those files’ contents as a mask of only the first 4 bytes (uncheck for exact matching). A progress line shows <em>N / total files</em> with the current file path while scanning; during the scan the options are hidden and the buttons are locked. Rules are created only for files that actually exist on the card (a FCP template is returned); missing files are skipped. Custom files from the <strong>Custom files</strong> sub-tab are included under the same existence check.</p>
|
<p class="text-sm mb-2">The scan dialog asks for a profile name and offers a <strong>“FCP/FCI check”</strong> selector (the same three modes above, default <strong>Filetype + size</strong>) applied to every generated rule, plus an <strong>“Ignore contents of files”</strong> checklist (all checked by default except <code class="font-mono text-sm">EF.ARR</code>; the header checkbox checks or unchecks the whole list) of frequently-overwritten files whose contents are skipped: <code class="font-mono text-sm">EF.LOCI</code>, <code class="font-mono text-sm">EF.PSLOCI</code>, <code class="font-mono text-sm">EF.EPSLOCI</code>, <code class="font-mono text-sm">EF.5GS3GPPLOCI</code>, <code class="font-mono text-sm">EF.Keys</code>, <code class="font-mono text-sm">EF.KeysPS</code>, <code class="font-mono text-sm">EF.SMS</code>, <code class="font-mono text-sm">EF.Kc</code>, <code class="font-mono text-sm">EF.KcGPRS</code>, <code class="font-mono text-sm">EF.LOCIGPRS</code>, <code class="font-mono text-sm">EF.CBMID</code>, <code class="font-mono text-sm">EF.SMSS</code>, <code class="font-mono text-sm">EF.ACC</code>, <code class="font-mono text-sm">EF.EPSNSC</code>, <code class="font-mono text-sm">EF.START-HFN</code>, <code class="font-mono text-sm">EF.ARR</code>. Two further checked-by-default options <strong>“Match first 4 bytes for”</strong> <code class="font-mono text-sm">EF.IMSI</code> and <code class="font-mono text-sm">EF.ICCID</code> capture those files’ contents as a mask of only the first 4 bytes (uncheck for exact matching). A progress line shows <em>N / total files</em> with the current file path while scanning; during the scan the options are hidden and the buttons are locked. Rules are created only for files that actually exist on the card (a FCP template is returned); missing files are skipped. Custom files from the <strong>Custom files</strong> sub-tab are included under the same existence check.</p>
|
||||||
|
|
||||||
<h4 id="card-snapshots" class="font-medium mb-1">Card snapshots</h4>
|
<h4 id="card-snapshots" class="font-medium mb-1">Card snapshots</h4>
|
||||||
<p class="text-sm mb-2">The list view shows two columns — <strong>Profiles</strong> and <strong>Card snapshots</strong>. A card snapshot is an immutable capture of the card filesystem: for every existing file it stores the path, symbolic name, file type, size (or record length/count), the raw FCI from the SELECT response, and the contents whenever the file is readable (no ignore list, no masking). The ICCID is decoded from EF.ICCID and shown next to the snapshot name.</p>
|
<p class="text-sm mb-2">The list view has two tabs — <strong>Profiles</strong> and <strong>Card snapshots</strong>. A card snapshot is an immutable capture of the card filesystem: for every existing file it stores the path, symbolic name, file type, size (or record length/count), the raw FCI from the SELECT response, and the contents whenever the file is readable (no ignore list, no masking). The ICCID is decoded from EF.ICCID and shown next to the snapshot name.</p>
|
||||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||||
<li><strong>New snapshot</strong> — asks for a name and scans the card, then returns to the list.</li>
|
<li><strong>New snapshot</strong> — asks for a name and scans the card, then returns to the list.</li>
|
||||||
<li><strong>Import snapshot</strong> — loads a snapshot from a JSON file.</li>
|
<li><strong>Import snapshot</strong> — loads a snapshot from a JSON file.</li>
|
||||||
|
|||||||
+42
-19
@@ -813,26 +813,26 @@
|
|||||||
|
|
||||||
<div id="pysim-sub-profiler" class="hidden">
|
<div id="pysim-sub-profiler" class="hidden">
|
||||||
<div id="profiler-list">
|
<div id="profiler-list">
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="flex justify-center gap-2 mb-3">
|
||||||
<div>
|
<button class="profiler-list-tab px-4 py-1.5 text-sm rounded-full bg-blue-600 text-white" data-list-tab="profiles" onclick="profilerListSwitch('profiles')" data-l10n="Profiles">Profiles</button>
|
||||||
<div class="text-sm font-semibold mb-2 text-gray-700 dark:text-slate-300" data-l10n="Profiles">Profiles</div>
|
<button class="profiler-list-tab px-4 py-1.5 text-sm rounded-full bg-gray-200 dark:bg-slate-700 text-gray-700 dark:text-slate-300 hover:bg-gray-300 dark:hover:bg-slate-600" data-list-tab="snapshots" onclick="profilerListSwitch('snapshots')" data-l10n="Card snapshots">Card snapshots</button>
|
||||||
<div class="flex flex-wrap gap-2 mb-3">
|
</div>
|
||||||
<button onclick="profilerNew()" class="px-2.5 py-1 text-sm rounded bg-blue-600 text-white hover:bg-blue-700" data-l10n="New profile">New profile</button>
|
<div id="profiler-list-profiles">
|
||||||
<button onclick="profilerFromCard()" class="px-2.5 py-1 text-sm rounded bg-blue-600 text-white hover:bg-blue-700" data-l10n="Profile from card">Profile from card</button>
|
<div class="flex flex-wrap gap-2 mb-3">
|
||||||
<button onclick="document.getElementById('profiler-import-file').click()" class="px-2.5 py-1 text-sm rounded bg-blue-600 text-white hover:bg-blue-700" data-l10n="Import profile">Import profile</button>
|
<button onclick="profilerNew()" class="px-2.5 py-1 text-sm rounded bg-blue-600 text-white hover:bg-blue-700" data-l10n="New profile">New profile</button>
|
||||||
<input type="file" id="profiler-import-file" accept=".json,application/json" class="hidden" onchange="profilerImportFile(this)">
|
<button onclick="profilerFromCard()" class="px-2.5 py-1 text-sm rounded bg-blue-600 text-white hover:bg-blue-700" data-l10n="Profile from card">Profile from card</button>
|
||||||
</div>
|
<button onclick="document.getElementById('profiler-import-file').click()" class="px-2.5 py-1 text-sm rounded bg-blue-600 text-white hover:bg-blue-700" data-l10n="Import profile">Import profile</button>
|
||||||
<div id="profiler-list-body"></div>
|
<input type="file" id="profiler-import-file" accept=".json,application/json" class="hidden" onchange="profilerImportFile(this)">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div id="profiler-list-body"></div>
|
||||||
<div class="text-sm font-semibold mb-2 text-gray-700 dark:text-slate-300" data-l10n="Card snapshots">Card snapshots</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-2 mb-3">
|
<div id="profiler-list-snapshots" class="hidden">
|
||||||
<button onclick="snapshotNew()" class="px-2.5 py-1 text-sm rounded bg-blue-600 text-white hover:bg-blue-700" data-l10n="New snapshot">New snapshot</button>
|
<div class="flex flex-wrap gap-2 mb-3">
|
||||||
<button onclick="document.getElementById('snapshot-import-file').click()" class="px-2.5 py-1 text-sm rounded bg-blue-600 text-white hover:bg-blue-700" data-l10n="Import snapshot">Import snapshot</button>
|
<button onclick="snapshotNew()" class="px-2.5 py-1 text-sm rounded bg-blue-600 text-white hover:bg-blue-700" data-l10n="New snapshot">New snapshot</button>
|
||||||
<input type="file" id="snapshot-import-file" accept=".json,application/json" class="hidden" onchange="snapshotImportFile(this)">
|
<button onclick="document.getElementById('snapshot-import-file').click()" class="px-2.5 py-1 text-sm rounded bg-blue-600 text-white hover:bg-blue-700" data-l10n="Import snapshot">Import snapshot</button>
|
||||||
</div>
|
<input type="file" id="snapshot-import-file" accept=".json,application/json" class="hidden" onchange="snapshotImportFile(this)">
|
||||||
<div id="snapshot-list-body"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div id="snapshot-list-body"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="profiler-editor" class="hidden">
|
<div id="profiler-editor" class="hidden">
|
||||||
@@ -6981,6 +6981,7 @@ let profilerResults = null;
|
|||||||
let snapshots = [];
|
let snapshots = [];
|
||||||
let snapshotViewId = null;
|
let snapshotViewId = null;
|
||||||
let _scanTarget = 'profile';
|
let _scanTarget = 'profile';
|
||||||
|
let profilerListTab = 'profiles';
|
||||||
|
|
||||||
// Files whose contents are ignored (contents check omitted) when scanning.
|
// Files whose contents are ignored (contents check omitted) when scanning.
|
||||||
// Keyed by FID; display name is the pySim name.
|
// Keyed by FID; display name is the pySim name.
|
||||||
@@ -7136,13 +7137,32 @@ function profilerRenderList() {
|
|||||||
el.innerHTML = html;
|
el.innerHTML = html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function profilerListSwitch(name) {
|
||||||
|
profilerListTab = name;
|
||||||
|
document.querySelectorAll('.profiler-list-tab').forEach(btn => {
|
||||||
|
const active = btn.dataset.listTab === name;
|
||||||
|
btn.classList.toggle('bg-blue-600', active);
|
||||||
|
btn.classList.toggle('text-white', active);
|
||||||
|
btn.classList.toggle('bg-gray-200', !active);
|
||||||
|
btn.classList.toggle('dark:bg-slate-700', !active);
|
||||||
|
btn.classList.toggle('text-gray-700', !active);
|
||||||
|
btn.classList.toggle('dark:text-slate-300', !active);
|
||||||
|
});
|
||||||
|
document.getElementById('profiler-list-profiles').classList.toggle('hidden', name !== 'profiles');
|
||||||
|
document.getElementById('profiler-list-snapshots').classList.toggle('hidden', name !== 'snapshots');
|
||||||
|
}
|
||||||
|
|
||||||
function profilerSetView(view) {
|
function profilerSetView(view) {
|
||||||
profilerView = view;
|
profilerView = view;
|
||||||
document.getElementById('profiler-list').classList.toggle('hidden', view !== 'list');
|
document.getElementById('profiler-list').classList.toggle('hidden', view !== 'list');
|
||||||
document.getElementById('profiler-editor').classList.toggle('hidden', view !== 'editor');
|
document.getElementById('profiler-editor').classList.toggle('hidden', view !== 'editor');
|
||||||
document.getElementById('profiler-results').classList.toggle('hidden', view !== 'results');
|
document.getElementById('profiler-results').classList.toggle('hidden', view !== 'results');
|
||||||
document.getElementById('profiler-snapshot').classList.toggle('hidden', view !== 'snapshot');
|
document.getElementById('profiler-snapshot').classList.toggle('hidden', view !== 'snapshot');
|
||||||
if (view === 'list') { profilerRenderList(); profilerRenderSnapshotList(); }
|
if (view === 'list') {
|
||||||
|
profilerRenderList();
|
||||||
|
profilerRenderSnapshotList();
|
||||||
|
profilerListSwitch(profilerListTab);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function profilerRenderSnapshotList() {
|
function profilerRenderSnapshotList() {
|
||||||
@@ -7296,6 +7316,7 @@ async function profilerScanStart() {
|
|||||||
snapshotsSave();
|
snapshotsSave();
|
||||||
profilerScanCancel();
|
profilerScanCancel();
|
||||||
profilerSetView('list');
|
profilerSetView('list');
|
||||||
|
profilerListSwitch('snapshots');
|
||||||
} else {
|
} else {
|
||||||
const rules = await profilerScanCard(ignoreFids, ignoreNames, fciMode, onProgress, maskFids);
|
const rules = await profilerScanCard(ignoreFids, ignoreNames, fciMode, onProgress, maskFids);
|
||||||
const profile = { id: profilerNewId(), name: name, created: new Date().toISOString(), rules: rules };
|
const profile = { id: profilerNewId(), name: name, created: new Date().toISOString(), rules: rules };
|
||||||
@@ -7481,6 +7502,7 @@ function profilerImportFile(input) {
|
|||||||
profiles.push(obj);
|
profiles.push(obj);
|
||||||
profilerSave();
|
profilerSave();
|
||||||
profilerRenderList();
|
profilerRenderList();
|
||||||
|
profilerListSwitch('profiles');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('Import error: ' + e.message);
|
alert('Import error: ' + e.message);
|
||||||
}
|
}
|
||||||
@@ -7544,6 +7566,7 @@ function snapshotImportFile(input) {
|
|||||||
snapshots.push(obj);
|
snapshots.push(obj);
|
||||||
snapshotsSave();
|
snapshotsSave();
|
||||||
profilerRenderSnapshotList();
|
profilerRenderSnapshotList();
|
||||||
|
profilerListSwitch('snapshots');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('Import error: ' + e.message);
|
alert('Import error: ' + e.message);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'otaman-v81';
|
const CACHE = 'otaman-v82';
|
||||||
const URLS = [
|
const URLS = [
|
||||||
'index.html',
|
'index.html',
|
||||||
'help.html',
|
'help.html',
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ function extractFunc(src, name, asyncFn) {
|
|||||||
return (asyncFn ? 'async ' : '') + src.slice(m.index, i + 1);
|
return (asyncFn ? 'async ' : '') + src.slice(m.index, i + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const FNS = ['profilerNormHex', 'profilerNormHexStrict', 'profilerMatch', 'profilerMatchMin', 'profilerMaskPrefix4', 'profilerFileFields', 'profilerContentKindForFileType', 'profilerEmptyRecordContent', 'profilerValidateProfile', 'profilerCustomNameForPath', 'profilerUpdateRulePath', 'profilerResultAspects', 'profilerAspectSummary', 'profilerNumRanges', 'esc', 'escHtml', 'profilerRawDataCheck', 'profilerRenderReport', 'parseBerLen', 'parseTlvList', 'fcpInt', 'fcpParseTlvs', 'fcpFileDescriptor', 'fcpLifeCycle', 'fcpSfi', 'fcpDo', 'fcpDecode', 'fcpDiffHtml', 'profilerFciPreviewItems', 'profilerUpdateFciPreview', 'profilerUpdateRule', 'profilerFciInput', 'profilerScanToggleAll', 'profilerScanIgnoreAllState', 'swapNibbles', 'decIccid', 'profilerSnapshotIccid', 'profilerValidateSnapshot'];
|
const FNS = ['profilerNormHex', 'profilerNormHexStrict', 'profilerMatch', 'profilerMatchMin', 'profilerMaskPrefix4', 'profilerFileFields', 'profilerContentKindForFileType', 'profilerEmptyRecordContent', 'profilerValidateProfile', 'profilerCustomNameForPath', 'profilerUpdateRulePath', 'profilerResultAspects', 'profilerAspectSummary', 'profilerNumRanges', 'esc', 'escHtml', 'profilerRawDataCheck', 'profilerRenderReport', 'parseBerLen', 'parseTlvList', 'fcpInt', 'fcpParseTlvs', 'fcpFileDescriptor', 'fcpLifeCycle', 'fcpSfi', 'fcpDo', 'fcpDecode', 'fcpDiffHtml', 'profilerFciPreviewItems', 'profilerUpdateFciPreview', 'profilerUpdateRule', 'profilerFciInput', 'profilerScanToggleAll', 'profilerScanIgnoreAllState', 'swapNibbles', 'decIccid', 'profilerSnapshotIccid', 'profilerValidateSnapshot', 'profilerListSwitch'];
|
||||||
let code = '';
|
let code = '';
|
||||||
for (const f of FNS) code += extractFunc(html, f) + '\n';
|
for (const f of FNS) code += extractFunc(html, f) + '\n';
|
||||||
code += extractFunc(html, 'profilerBuildFileRule', true) + '\n';
|
code += extractFunc(html, 'profilerBuildFileRule', true) + '\n';
|
||||||
@@ -914,3 +914,31 @@ test('profilerScanCard snapshot mode builds snapshot entries with ICCID', async
|
|||||||
assert.strictEqual(files.find(f => f.name === 'EF.IMSI').content.data, '082905911234567890');
|
assert.strictEqual(files.find(f => f.name === 'EF.IMSI').content.data, '082905911234567890');
|
||||||
delete global.pysimCustomFiles;
|
delete global.pysimCustomFiles;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('profilerListSwitch toggles the profiles/snapshots tabs', () => {
|
||||||
|
const mkBtn = tab => ({
|
||||||
|
dataset: { listTab: tab },
|
||||||
|
classList: { _c: new Set(), toggle(c, on) { if (on) this._c.add(c); else this._c.delete(c); } },
|
||||||
|
});
|
||||||
|
const btns = [mkBtn('profiles'), mkBtn('snapshots')];
|
||||||
|
const profilesEl = { hidden: false, classList: { toggle(cls, on) { profilesEl.hidden = on; } } };
|
||||||
|
const snapsEl = { hidden: true, classList: { toggle(cls, on) { snapsEl.hidden = on; } } };
|
||||||
|
global.document = {
|
||||||
|
querySelectorAll: sel => (sel === '.profiler-list-tab' ? btns : []),
|
||||||
|
getElementById: id => (id === 'profiler-list-profiles' ? profilesEl : id === 'profiler-list-snapshots' ? snapsEl : null),
|
||||||
|
};
|
||||||
|
|
||||||
|
profilerListSwitch('snapshots');
|
||||||
|
assert.strictEqual(profilesEl.hidden, true);
|
||||||
|
assert.strictEqual(snapsEl.hidden, false);
|
||||||
|
assert.ok(btns[0].classList._c.has('bg-gray-200'));
|
||||||
|
assert.ok(btns[1].classList._c.has('bg-blue-600'));
|
||||||
|
|
||||||
|
profilerListSwitch('profiles');
|
||||||
|
assert.strictEqual(profilesEl.hidden, false);
|
||||||
|
assert.strictEqual(snapsEl.hidden, true);
|
||||||
|
assert.ok(btns[0].classList._c.has('bg-blue-600'));
|
||||||
|
assert.ok(btns[1].classList._c.has('bg-gray-200'));
|
||||||
|
|
||||||
|
delete global.document;
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user