ui: split Phone simulator into Phone and TR Config pills
- Phone pill: STK menu, STATUS and polling and the subscribed-events list
in one row, proactive command log full-width below (room for more
elements)
- TR Config pill: response data injected into TERMINAL RESPONSEs;
currently the PROVIDE LOCAL INFORMATION dictionary, structured as
heading + body blocks for future proactive-command responses
- phoneSwitchSubtab() mirrors the other sub-tab switchers and sets help
anchors stk-menu / pli-dict; switchTab('phone') always opens Phone
- refreshDynamicI18n renders only the visible phone panel
- help EN/RU section 6 regrouped (6.4 STATUS polling under Phone, 6.5
TR Config response data), READMEs and AGENTS.md updated
- structural and behavioral tests (html.test.js, phone_tabs.test.js);
SW cache v93 -> v94
This commit is contained in:
@@ -408,9 +408,9 @@
|
||||
|
||||
<section class="mb-10">
|
||||
<h2 id="proactive-uicc" class="text-xl font-semibold mb-3 border-b border-gray-300 dark:border-slate-700 pb-1">6. Симулятор телефона</h2>
|
||||
<p class="text-sm mb-3">Работа с сессией Card Application Toolkit: меню STK, подписанные события, журнал проактивных команд, словарь данных PROVIDE LOCAL INFORMATION и опрос STATUS.</p>
|
||||
<p class="text-sm mb-3">Работа с сессией Card Application Toolkit. Две подвкладки: <strong>«Телефон»</strong> (меню STK, STATUS и опрос, подписанные события, журнал проактивных команд) и <strong>«Конфигурация TR»</strong> (данные ответов, подставляемые в TERMINAL RESPONSE для проактивных команд).</p>
|
||||
|
||||
<h3 id="stk-menu" class="text-lg font-medium mb-2">Меню STK</h3>
|
||||
<h3 id="stk-menu" class="text-lg font-medium mb-2">6.1 Меню STK</h3>
|
||||
<p class="text-sm mb-3">Если карта выдала команду SET UP MENU, вверху этого представления появляется блок «Меню STK» с изумрудной кнопкой <strong>STK: <название></strong>, открывающей оверлей меню (браузер STK-меню карты). Если карта не задала меню, вместо кнопки показывается «Меню не задано картой». Состояние меню обновляется при каждом открытии представления. Интерактивные проактивные команды всегда получают TERMINAL RESPONSE: оверлей ждёт вашего выбора, и если вы не ответили и не нажали <strong>Timeout</strong>, сервер сам отвечает результатом timeout через <code class="font-mono text-sm">--menu-timeout</code> секунд (по умолчанию 60, <code class="font-mono text-sm">0</code> отключает).</p>
|
||||
|
||||
<h3 id="subscribed-events" class="text-lg font-medium mb-2">6.2 Подписанные события (SET UP EVENT LIST)</h3>
|
||||
@@ -426,7 +426,10 @@
|
||||
<h3 id="proactive-log" class="text-lg font-medium mb-2">6.3 Журнал проактивных команд</h3>
|
||||
<p class="text-sm mb-2">Хронологический список извлечённых проактивных команд. Каждая строка показывает время, код типа, имя и декодированный квалификатор (для команд, у которых он есть). Для команд с данными ответа показывается строка <code class="font-mono text-sm">Ответ:</code> с байтами TERMINAL RESPONSE (без служебных TLV); ответы PROVIDE LOCAL INFORMATION декодируются через словарь данных PLI.</p>
|
||||
|
||||
<h3 id="pli-dict" class="text-lg font-medium mb-2">6.4 Словарь данных PROVIDE LOCAL INFORMATION</h3>
|
||||
<h3 id="status-polling" class="text-lg font-medium mb-2">6.4 Опрос STATUS</h3>
|
||||
<p class="text-sm mb-3">Кнопка <strong>Отправить STATUS</strong> отправляет STATUS (F2) вручную. Переключатель <strong>Опрос</strong> включает фоновый опрос: после настраиваемого интервала бездействия (аргумент сервера <code class="font-mono text-sm">--poll-interval</code>, 1–255 с, по умолчанию 30 с, <code class="font-mono text-sm">0</code> отключает опрос) сервер отправляет STATUS и обрабатывает любую ожидающую проактивную команду. При извлечении карты опрос останавливается, а состояние карты сбрасывается.</p>
|
||||
|
||||
<h3 id="pli-dict" class="text-lg font-medium mb-2">6.5 «Конфигурация TR» — данные ответа PROVIDE LOCAL INFORMATION</h3>
|
||||
<p class="text-sm mb-2">Редактируемые hex-значения для всех 22 квалификаторов PLI (TS 102 223 §8.6 + TS 131 111). У десяти квалификаторов есть встроенные формы декодирования/кодирования:</p>
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>00</strong> Location Info (MCC, MNC, LAC/TAC, Cell ID)</li>
|
||||
@@ -436,9 +439,6 @@
|
||||
</ul>
|
||||
<p class="text-sm mb-3">Значения хранятся на сервере до перезапуска. Когда карта выдаёт PLI, сервер вставляет значения словаря в TERMINAL RESPONSE.</p>
|
||||
|
||||
<h3 id="status-polling" class="text-lg font-medium mb-2">6.5 Опрос STATUS</h3>
|
||||
<p class="text-sm mb-3">Кнопка <strong>Отправить STATUS</strong> отправляет STATUS (F2) вручную. Переключатель <strong>Опрос</strong> включает фоновый опрос: после настраиваемого интервала бездействия (аргумент сервера <code class="font-mono text-sm">--poll-interval</code>, 1–255 с, по умолчанию 30 с, <code class="font-mono text-sm">0</code> отключает опрос) сервер отправляет STATUS и обрабатывает любую ожидающую проактивную команду. При извлечении карты опрос останавливается, а состояние карты сбрасывается.</p>
|
||||
|
||||
</section>
|
||||
|
||||
<section class="mb-10">
|
||||
|
||||
+6
-6
@@ -408,9 +408,9 @@
|
||||
|
||||
<section class="mb-10">
|
||||
<h2 id="proactive-uicc" class="text-xl font-semibold mb-3 border-b border-gray-300 dark:border-slate-700 pb-1">6. Phone simulator</h2>
|
||||
<p class="text-sm mb-3">Interacts with the Card Application Toolkit session: the STK menu, subscribed events, the proactive command log, the PROVIDE LOCAL INFORMATION data dictionary, and STATUS polling.</p>
|
||||
<p class="text-sm mb-3">Interacts with the Card Application Toolkit session. The view has two pills: <strong>Phone</strong> (STK menu, STATUS and polling, subscribed events, proactive command log) and <strong>TR Config</strong> (response data injected into TERMINAL RESPONSEs for proactive commands).</p>
|
||||
|
||||
<h3 id="stk-menu" class="text-lg font-medium mb-2">STK menu</h3>
|
||||
<h3 id="stk-menu" class="text-lg font-medium mb-2">6.1 STK menu</h3>
|
||||
<p class="text-sm mb-3">When the card has issued a SET UP MENU command, a “STK menu” block appears at the top of this view with an emerald <strong>STK: <title></strong> button that opens the menu overlay (same as the card’s STK menu browser). If the card has not set up a menu, the block shows “No menu set by the card” instead. The menu state is refreshed each time the view is opened. User-interactive proactive commands always get a TERMINAL RESPONSE: the overlay pauses for your choice, and if you neither answer nor press <strong>Timeout</strong>, the server answers with a timeout result after the <code class="font-mono text-sm">--menu-timeout</code> seconds (default 60, <code class="font-mono text-sm">0</code> disables).</p>
|
||||
|
||||
<h3 id="subscribed-events" class="text-lg font-medium mb-2">6.2 Subscribed events (SET UP EVENT LIST)</h3>
|
||||
@@ -426,7 +426,10 @@
|
||||
<h3 id="proactive-log" class="text-lg font-medium mb-2">6.3 Proactive command log</h3>
|
||||
<p class="text-sm mb-2">Chronological list of fetched proactive commands. Each row shows the elapsed time, type code, name, and a decoded qualifier (for commands that have one). Commands with response data show a <code class="font-mono text-sm">Response:</code> line with the TERMINAL RESPONSE bytes (boilerplate TLVs stripped); PROVIDE LOCAL INFORMATION responses are decoded using the PLI data dictionary decoders.</p>
|
||||
|
||||
<h3 id="pli-dict" class="text-lg font-medium mb-2">6.4 PROVIDE LOCAL INFORMATION data dictionary</h3>
|
||||
<h3 id="status-polling" class="text-lg font-medium mb-2">6.4 STATUS polling</h3>
|
||||
<p class="text-sm mb-3">A <strong>Send STATUS</strong> button issues a manual STATUS (F2). A <strong>Polling</strong> toggle enables background polling: after a configurable idle interval (server CLI <code class="font-mono text-sm">--poll-interval</code>, 1–255 s, default 30 s, <code class="font-mono text-sm">0</code> disables polling) the server sends STATUS and handles any pending proactive command. Polling stops and card state resets if the card is removed.</p>
|
||||
|
||||
<h3 id="pli-dict" class="text-lg font-medium mb-2">6.5 TR Config — PROVIDE LOCAL INFORMATION response data</h3>
|
||||
<p class="text-sm mb-2">Editable hex values for all 22 PLI qualifiers (TS 102 223 §8.6 + TS 131 111). Ten qualifiers have inline decode/encode forms:</p>
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>00</strong> Location Info (MCC, MNC, LAC/TAC, Cell ID)</li>
|
||||
@@ -436,9 +439,6 @@
|
||||
</ul>
|
||||
<p class="text-sm mb-3">Values persist server-side until restart. When the card issues PLI, the server injects the dictionary values into the TERMINAL RESPONSE.</p>
|
||||
|
||||
<h3 id="status-polling" class="text-lg font-medium mb-2">6.5 STATUS polling</h3>
|
||||
<p class="text-sm mb-3">A <strong>Send STATUS</strong> button issues a manual STATUS (F2). A <strong>Polling</strong> toggle enables background polling: after a configurable idle interval (server CLI <code class="font-mono text-sm">--poll-interval</code>, 1–255 s, default 30 s, <code class="font-mono text-sm">0</code> disables polling) the server sends STATUS and handles any pending proactive command. Polling stops and card state resets if the card is removed.</p>
|
||||
|
||||
</section>
|
||||
|
||||
<section class="mb-10">
|
||||
|
||||
+60
-22
@@ -897,37 +897,47 @@
|
||||
</div>
|
||||
|
||||
<div id="tab-phone" class="tab-content hidden">
|
||||
<div class="flex gap-6">
|
||||
<div class="flex flex-wrap justify-center gap-2 mb-3">
|
||||
<button class="phone-subtab px-4 py-1.5 text-sm rounded-full bg-blue-600 text-white" data-phone-sub="phone" onclick="phoneSwitchSubtab('phone')" data-l10n="Phone">Phone</button>
|
||||
<button class="phone-subtab 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-phone-sub="tr" onclick="phoneSwitchSubtab('tr')" data-l10n="TR Config">TR Config</button>
|
||||
</div>
|
||||
<div id="phone-sub-phone">
|
||||
<div class="flex flex-wrap gap-6 mb-4">
|
||||
<div>
|
||||
<div class="mb-1 text-sm text-gray-500 dark:text-slate-400" data-l10n="STK menu">STK menu</div>
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<button id="stk-menu-btn" onclick="stkMenuOpen()" class="px-2.5 py-1 text-sm rounded bg-emerald-600 text-white hover:bg-emerald-700" style="display:none">STK: <span id="stk-menu-title"></span></button>
|
||||
<span id="stk-menu-none" class="text-xs text-gray-500 dark:text-slate-400" style="display:none" data-l10n="No menu set by the card">No menu set by the card</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button id="stk-menu-btn" onclick="stkMenuOpen()" class="px-2.5 py-1 text-sm rounded bg-emerald-600 text-white hover:bg-emerald-700" style="display:none">STK: <span id="stk-menu-title"></span></button>
|
||||
<span id="stk-menu-none" class="text-xs text-gray-500 dark:text-slate-400" style="display:none" data-l10n="No menu set by the card">No menu set by the card</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-1 text-sm text-gray-500 dark:text-slate-400" data-l10n="STATUS and Polling">STATUS and Polling</div>
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<div>
|
||||
<div class="mb-1 text-sm text-gray-500 dark:text-slate-400" data-l10n="STATUS and Polling">STATUS and Polling</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button id="pli-status-btn" onclick="pysimStatusPoll()" class="px-2.5 py-1 text-sm rounded bg-gray-600 text-white hover:bg-gray-700" data-l10n="Send STATUS">Send STATUS</button>
|
||||
<span class="text-xs text-gray-500 dark:text-slate-400">|</span>
|
||||
<span class="text-xs text-gray-500 dark:text-slate-400" data-l10n="Polling">Polling</span>
|
||||
<button id="pli-pause-btn" onclick="pysimPollToggle()" class="px-2.5 py-1 text-sm rounded bg-gray-200 dark:bg-slate-700 text-gray-700 dark:text-slate-300 hover:bg-gray-300 dark:hover:bg-slate-600">OFF</button>
|
||||
<span id="pli-poll-stat" class="text-xs text-gray-400"></span>
|
||||
</div>
|
||||
<div class="mt-2 mb-2 text-sm text-gray-500 dark:text-slate-400" data-l10n="Events that the card monitors (SET UP EVENT LIST):">Events that the card monitors (SET UP EVENT LIST):</div>
|
||||
<div id="pysim-event-list" class="text-sm font-mono text-gray-600 dark:text-slate-400"></div>
|
||||
<div class="mt-4 mb-2 text-sm text-gray-500 dark:text-slate-400" data-l10n="Fetched proactive commands:">Fetched proactive commands:</div>
|
||||
<div id="pysim-proactive-log" class="text-sm font-mono text-gray-600 dark:text-slate-400"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-sm text-gray-500 dark:text-slate-400" data-l10n="PROVIDE LOCAL INFORMATION response data">PROVIDE LOCAL INFORMATION response data</span>
|
||||
<button id="pli-save-btn" onclick="pysimPliSave()" class="px-2.5 py-1 text-sm rounded bg-emerald-600 text-white hover:bg-emerald-700" data-l10n="Save">Save</button>
|
||||
</div>
|
||||
<div id="pysim-pli-dict" class="text-sm font-mono text-gray-600 dark:text-slate-400 max-h-[60vh] overflow-auto">
|
||||
<span class="text-gray-400" data-l10n="Loading...">Loading...</span>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="mb-1 text-sm text-gray-500 dark:text-slate-400" data-l10n="Events that the card monitors (SET UP EVENT LIST):">Events that the card monitors (SET UP EVENT LIST):</div>
|
||||
<div id="pysim-event-list" class="text-sm font-mono text-gray-600 dark:text-slate-400"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2 text-sm text-gray-500 dark:text-slate-400" data-l10n="Fetched proactive commands:">Fetched proactive commands:</div>
|
||||
<div id="pysim-proactive-log" class="text-sm font-mono text-gray-600 dark:text-slate-400"></div>
|
||||
</div>
|
||||
<div id="phone-sub-tr" class="hidden">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-sm text-gray-500 dark:text-slate-400" data-l10n="PROVIDE LOCAL INFORMATION response data">PROVIDE LOCAL INFORMATION response data</span>
|
||||
<button id="pli-save-btn" onclick="pysimPliSave()" class="px-2.5 py-1 text-sm rounded bg-emerald-600 text-white hover:bg-emerald-700" data-l10n="Save">Save</button>
|
||||
</div>
|
||||
<div id="pysim-pli-dict" class="text-sm font-mono text-gray-600 dark:text-slate-400 max-h-[60vh] overflow-auto">
|
||||
<span class="text-gray-400" data-l10n="Loading...">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="event-send-modal" class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center hidden">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg p-6 max-w-sm w-full mx-4 max-h-[85vh] overflow-auto">
|
||||
@@ -1030,7 +1040,7 @@ function switchTab(name) {
|
||||
else if (name === 'scp80') setHelpAnchor(scp80HelpAnchor);
|
||||
else if (name === 'pysim') setHelpAnchor(pysimHelpAnchor);
|
||||
else if (name === 'profiler') { profilerSetView('list'); setHelpAnchor('profiler'); }
|
||||
else if (name === 'phone') { stkCheckMenu(); pysimEventsRender(); pysimProactiveLogRender(); pysimPliRender(); pysimPollStatusInit(); setHelpAnchor('proactive-uicc'); }
|
||||
else if (name === 'phone') phoneSwitchSubtab('phone');
|
||||
}
|
||||
|
||||
let scp80HelpAnchor = 'secured-packet';
|
||||
@@ -1070,6 +1080,30 @@ function cApduSwitchSubtab(name) {
|
||||
setHelpAnchor({sim:'sim-rfm', usim:'usim-rfm', ber:'ber-tlv', ram:'ram-gp', parse:'c-apdu-parser', httpota:'http-ota', response:'response-parser'}[name] || 'c-apdu');
|
||||
}
|
||||
|
||||
function phoneSwitchSubtab(name) {
|
||||
document.querySelectorAll('.phone-subtab').forEach(btn => {
|
||||
const active = btn.dataset.phoneSub === 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);
|
||||
});
|
||||
['phone', 'tr'].forEach(s => {
|
||||
document.getElementById('phone-sub-' + s).classList.toggle('hidden', s !== name);
|
||||
});
|
||||
if (name === 'tr') {
|
||||
pysimPliRender();
|
||||
} else {
|
||||
stkCheckMenu();
|
||||
pysimEventsRender();
|
||||
pysimProactiveLogRender();
|
||||
pysimPollStatusInit();
|
||||
}
|
||||
setHelpAnchor({ phone: 'stk-menu', tr: 'pli-dict' }[name] || 'stk-menu');
|
||||
}
|
||||
|
||||
// ===== HTTP OTA constructor =====
|
||||
let _hotaMode = 'trigger';
|
||||
function _hotaHex(s) { return (s || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase(); }
|
||||
@@ -8802,6 +8836,8 @@ const LANG_RU = {
|
||||
'Verify vs pySim': 'Проверить в pySim',
|
||||
'Send to Card': 'Отправить на карту',
|
||||
'Phone simulator': 'Симулятор телефона',
|
||||
'Phone': 'Телефон',
|
||||
'TR Config': 'Конфигурация TR',
|
||||
'Events that the card monitors (SET UP EVENT LIST):': 'События, отслеживаемые картой (SET UP EVENT LIST):',
|
||||
'No events configured.': 'Нет настроенных событий.',
|
||||
'Fetched proactive commands:': 'Извлечённые проактивные команды:',
|
||||
@@ -8988,9 +9024,11 @@ function refreshDynamicI18n() {
|
||||
if (isViewVisible('profiler-scan-modal')) profilerScanRefreshOptions();
|
||||
if (isViewVisible('scp80-sub-cards')) cardsRender();
|
||||
if (isViewVisible('tab-phone')) {
|
||||
pysimEventsRender();
|
||||
pysimProactiveLogRender();
|
||||
pysimPliRender();
|
||||
if (isViewVisible('phone-sub-phone')) {
|
||||
pysimEventsRender();
|
||||
pysimProactiveLogRender();
|
||||
}
|
||||
if (isViewVisible('phone-sub-tr')) pysimPliRender();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'otaman-v93';
|
||||
const CACHE = 'otaman-v94';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -26,3 +26,10 @@ test('profiler and phone simulator are top-level tab contents', () => {
|
||||
assert.ok(html.includes('id="tab-profiler" class="tab-content hidden"'));
|
||||
assert.ok(html.includes('id="tab-phone" class="tab-content hidden"'));
|
||||
});
|
||||
|
||||
test('phone simulator has Phone / TR Config pills', () => {
|
||||
assert.match(html, /data-phone-sub="phone" onclick="phoneSwitchSubtab\('phone'\)"/);
|
||||
assert.match(html, /data-phone-sub="tr" onclick="phoneSwitchSubtab\('tr'\)"/);
|
||||
assert.ok(html.includes('id="phone-sub-phone"'));
|
||||
assert.ok(html.includes('id="phone-sub-tr"'));
|
||||
});
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
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);
|
||||
}
|
||||
|
||||
const code = extractFunc(html, 'phoneSwitchSubtab') + '\n' +
|
||||
'globalThis.setHelpAnchor = a => { globalThis._anchor = a; };\n' +
|
||||
'globalThis.stkCheckMenu = () => { globalThis._stk = (globalThis._stk || 0) + 1; };\n' +
|
||||
'globalThis.pysimEventsRender = () => { globalThis._events = (globalThis._events || 0) + 1; };\n' +
|
||||
'globalThis.pysimProactiveLogRender = () => { globalThis._log = (globalThis._log || 0) + 1; };\n' +
|
||||
'globalThis.pysimPollStatusInit = () => { globalThis._poll = (globalThis._poll || 0) + 1; };\n' +
|
||||
'globalThis.pysimPliRender = () => { globalThis._pli = (globalThis._pli || 0) + 1; };\n';
|
||||
eval(code);
|
||||
|
||||
function makeClassList() {
|
||||
const set = new Set();
|
||||
return {
|
||||
toggle: (c, on) => { on ? set.add(c) : set.delete(c); },
|
||||
has: c => set.has(c),
|
||||
};
|
||||
}
|
||||
|
||||
function setup() {
|
||||
const buttons = [
|
||||
{ dataset: { phoneSub: 'phone' }, classList: makeClassList() },
|
||||
{ dataset: { phoneSub: 'tr' }, classList: makeClassList() },
|
||||
];
|
||||
const panels = {
|
||||
'phone-sub-phone': { classList: makeClassList() },
|
||||
'phone-sub-tr': { classList: makeClassList() },
|
||||
};
|
||||
globalThis.document = {
|
||||
querySelectorAll: sel => (sel === '.phone-subtab' ? buttons : []),
|
||||
getElementById: id => panels[id] || null,
|
||||
};
|
||||
globalThis._anchor = null;
|
||||
globalThis._stk = globalThis._events = globalThis._log = globalThis._poll = globalThis._pli = 0;
|
||||
return { buttons, panels };
|
||||
}
|
||||
|
||||
test('TR Config pill shows the TR panel and renders PLI data', () => {
|
||||
const { buttons, panels } = setup();
|
||||
phoneSwitchSubtab('tr');
|
||||
assert.ok(!panels['phone-sub-tr'].classList.has('hidden'));
|
||||
assert.ok(panels['phone-sub-phone'].classList.has('hidden'));
|
||||
assert.ok(buttons[1].classList.has('bg-blue-600'));
|
||||
assert.ok(!buttons[0].classList.has('bg-blue-600'));
|
||||
assert.strictEqual(globalThis._anchor, 'pli-dict');
|
||||
assert.strictEqual(globalThis._pli, 1);
|
||||
assert.strictEqual(globalThis._stk, 0);
|
||||
});
|
||||
|
||||
test('Phone pill shows the phone panel and renders CAT views', () => {
|
||||
const { buttons, panels } = setup();
|
||||
phoneSwitchSubtab('phone');
|
||||
assert.ok(!panels['phone-sub-phone'].classList.has('hidden'));
|
||||
assert.ok(panels['phone-sub-tr'].classList.has('hidden'));
|
||||
assert.ok(buttons[0].classList.has('bg-blue-600'));
|
||||
assert.strictEqual(globalThis._anchor, 'stk-menu');
|
||||
assert.strictEqual(globalThis._stk, 1);
|
||||
assert.strictEqual(globalThis._events, 1);
|
||||
assert.strictEqual(globalThis._log, 1);
|
||||
assert.strictEqual(globalThis._poll, 1);
|
||||
assert.strictEqual(globalThis._pli, 0);
|
||||
});
|
||||
Reference in New Issue
Block a user