feat: answer GET INKEY / GET INPUT interactively (v3.6.38)
The menu chain paused only for DISPLAY TEXT / SELECT ITEM / SET UP MENU, so a GET INPUT was auto-answered with a bare success TR and no Text string - the live RemMobileID PIN prompt got an empty answer. Server: _parse_get_inkey/_parse_get_input read the prompt, qualifier flags (digits-only, UCS2, Yes/No, immediate, hidden, packed, help), Response length 91 and Default Text 9D; the fetch handler pauses with them; _menu_send_response(..., text=) validates the answer (digits set, min/max, one char for GET INKEY) and builds the Text string TLV 8D in the requested coding (SMS unpacked 04 / UCS2 08 / packed 00 via _pack_gsm7), Yes/No as 01/00, empty input as the null text string, help as result 13 and the variable-timeout Duration echoed (6.8.4). /api/menu-respond takes text; /api/stk-status carries pending_data so a card-initiated prompt opens the panel; _decode_cmd decodes both commands. PWA: stkInputHtml/stkInputHint/stkInputValidate render the constrained panel (password field for hidden entry, numeric keyboard, default text, Yes/No buttons, help, immediate-digit auto-submit); OK submits through stkMenuOk; the poll opens the panel for card-initiated prompts. sw cache simple-v311; tests: 517 Python / 674 Node.
This commit is contained in:
@@ -488,7 +488,7 @@
|
||||
<p class="text-sm mb-3">Работа с сессией Card Application Toolkit. Четыре подвкладки: <strong>«Телефон»</strong> (меню STK, STATUS и опрос, подписанные события, журнал проактивных команд), <strong>«Конфигурация TR»</strong> (данные ответов, подставляемые в TERMINAL RESPONSE для проактивных команд), <strong>«eSIM LPA»</strong> (локальные операции с eUICC) и <strong>«Тест-скрипт»</strong> (сценарные тест-скрипты карты с ожиданием проактивных команд, см. <a href="#test-script" class="text-blue-600 dark:text-blue-400 hover:underline">§8.9</a>).</p>
|
||||
|
||||
<h3 id="stk-menu" class="text-lg font-medium mb-2">8.1 Меню STK</h3>
|
||||
<p class="text-sm mb-3">Если карта выдала команду SET UP MENU, вверху этого представления появляется блок «Меню STK» с изумрудной кнопкой <strong>STK: <название></strong>, открывающей оверлей меню (браузер STK-меню карты). Если карта не задала меню, вместо кнопки показывается «Меню не задано картой». Состояние меню обновляется при каждом открытии представления. Пункты меню, для которых карта передала <strong>Next Action Indicator</strong> (TS 102 223 §8.24), показывают его серым суффиксом (например, <code class="font-mono text-sm">→ SET UP MENU</code>) — что карта выполнит при выборе пункта. Интерактивные проактивные команды всегда получают TERMINAL RESPONSE: оверлей ждёт вашего выбора, и если вы не ответили и не нажали <strong>Timeout</strong>, сервер сам отвечает результатом timeout через <code class="font-mono text-sm">--menu-timeout</code> секунд (по умолчанию 60, <code class="font-mono text-sm">0</code> отключает). <strong>Назад</strong> и <strong>Timeout</strong> продолжают диалог с картой: если карта в ответ выдаёт следующую проактивную команду (SELECT ITEM или DISPLAY TEXT), панель показывает её; кэшированное верхнее меню появляется только когда карте больше нечего выполнять.</p>
|
||||
<p class="text-sm mb-3">Если карта выдала команду SET UP MENU, вверху этого представления появляется блок «Меню STK» с изумрудной кнопкой <strong>STK: <название></strong>, открывающей оверлей меню (браузер STK-меню карты). Если карта не задала меню, вместо кнопки показывается «Меню не задано картой». Состояние меню обновляется при каждом открытии представления. Пункты меню, для которых карта передала <strong>Next Action Indicator</strong> (TS 102 223 §8.24), показывают его серым суффиксом (например, <code class="font-mono text-sm">→ SET UP MENU</code>) — что карта выполнит при выборе пункта. Интерактивные проактивные команды всегда получают TERMINAL RESPONSE: оверлей ждёт вашего выбора, и если вы не ответили и не нажали <strong>Timeout</strong>, сервер сам отвечает результатом timeout через <code class="font-mono text-sm">--menu-timeout</code> секунд (по умолчанию 60, <code class="font-mono text-sm">0</code> отключает). <strong>Назад</strong> и <strong>Timeout</strong> продолжают диалог с картой: если карта в ответ выдаёт следующую проактивную команду (SELECT ITEM или DISPLAY TEXT), панель показывает её; кэшированное верхнее меню появляется только когда карте больше нечего выполнять. Оверлей также отвечает на запросы карты <strong>GET INKEY</strong> и <strong>GET INPUT</strong> (TS 102 223 §6.4.2/§6.4.3): панель показывает текст карты и поле ввода, ограничения которого заданы командой (длина ответа min–max, цифровая клавиатура, скрытый ввод, подставленный текст по умолчанию, кнопки Да/Нет для GET INKEY, справка, если карта её объявила), а ответ возвращается в Text string внутри TERMINAL RESPONSE в запрошенной кодировке (SMS-алфавит unpacked, UCS2 или packed).</p>
|
||||
|
||||
<h3 id="subscribed-events" class="text-lg font-medium mb-2">8.2 Подписанные события (SET UP EVENT LIST)</h3>
|
||||
<p class="text-sm mb-2">События, которые отслеживает карта. У каждого события есть кнопка <strong>Отправить</strong>, открывающая форму, специфичную для типа события:</p>
|
||||
|
||||
+1
-1
@@ -487,7 +487,7 @@
|
||||
<p class="text-sm mb-3">Interacts with the Card Application Toolkit session. The view has four pills: <strong>Phone</strong> (STK menu, STATUS and polling, subscribed events, proactive command log), <strong>TR Config</strong> (response data injected into TERMINAL RESPONSEs for proactive commands), <strong>eSIM LPA</strong> (local eUICC operations) and <strong>Test script</strong> (scripted card test scripts with proactive-command expectations, see <a href="#test-script" class="text-blue-600 dark:text-blue-400 hover:underline">§8.9</a>).</p>
|
||||
|
||||
<h3 id="stk-menu" class="text-lg font-medium mb-2">8.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. Menu items whose card-provided <strong>Next Action Indicator</strong> (TS 102 223 §8.24) is present show it as a small gray suffix (e.g. <code class="font-mono text-sm">→ SET UP MENU</code>) — what the card would do when the item is selected. 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). <strong>Back</strong> and <strong>Timeout</strong> keep the dialogue with the card going: when the card replies with a further proactive command (SELECT ITEM or DISPLAY TEXT) the panel shows it; the cached top menu appears only when the card has nothing more to execute.</p>
|
||||
<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. Menu items whose card-provided <strong>Next Action Indicator</strong> (TS 102 223 §8.24) is present show it as a small gray suffix (e.g. <code class="font-mono text-sm">→ SET UP MENU</code>) — what the card would do when the item is selected. 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). <strong>Back</strong> and <strong>Timeout</strong> keep the dialogue with the card going: when the card replies with a further proactive command (SELECT ITEM or DISPLAY TEXT) the panel shows it; the cached top menu appears only when the card has nothing more to execute. The overlay also answers the card’s <strong>GET INKEY</strong> and <strong>GET INPUT</strong> prompts (TS 102 223 §6.4.2/§6.4.3): the panel shows the card’s text with an input field whose constraints come from the command (Response length min–max, digits-only keyboard, hidden entry, default text prefilled, Yes/No buttons for GET INKEY, help when the card advertises it), and the answer is returned in the TERMINAL RESPONSE’s Text string in the requested coding (SMS default alphabet unpacked, UCS2 or packed).</p>
|
||||
|
||||
<h3 id="subscribed-events" class="text-lg font-medium mb-2">8.2 Subscribed events (SET UP EVENT LIST)</h3>
|
||||
<p class="text-sm mb-2">The events the card monitors. Each event has a <strong>Send</strong> button that opens a form specific to the event type:</p>
|
||||
|
||||
+155
-2
@@ -1705,7 +1705,7 @@
|
||||
<div id="stk-menu-buttons" class="flex justify-between mt-3 pt-3 border-t border-gray-200 dark:border-slate-700 hidden">
|
||||
<button id="stk-timeout-btn" onclick="stkMenuRespond('timeout')" class="px-3 py-1 text-xs 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-l10n="Timeout">Timeout</button>
|
||||
<button id="stk-cancel-btn" onclick="stkMenuRespond('cancel')" class="px-3 py-1 text-xs 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-l10n="Cancel">Cancel</button>
|
||||
<button id="stk-ok-btn" onclick="stkMenuRespond('ok')" class="px-4 py-1 text-xs rounded-full bg-blue-600 text-white hover:bg-blue-700" data-l10n="OK">OK</button>
|
||||
<button id="stk-ok-btn" onclick="stkMenuOk()" class="px-4 py-1 text-xs rounded-full bg-blue-600 text-white hover:bg-blue-700" data-l10n="OK">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1732,7 +1732,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.6.37';
|
||||
const SIMPLE_VERSION = '3.6.38';
|
||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||
|
||||
// ===== Tab switching =====
|
||||
@@ -9688,10 +9688,126 @@ async function stkMenuItemClick(itemId) {
|
||||
}
|
||||
}
|
||||
|
||||
// User-input prompt for a pending GET INKEY / GET INPUT (pure). The card's
|
||||
// constraints drive the field: hidden entry masks the value (TS 102 223
|
||||
// 6.4.3), digits-only switches the keyboard, Response length bounds the text.
|
||||
function stkInputHtml(pd) {
|
||||
pd = pd || {};
|
||||
let html = '<div class="text-sm whitespace-pre-wrap p-2 bg-gray-50 dark:bg-slate-700 rounded mb-2">' + esc(pd.text || '') + '</div>';
|
||||
if (pd.yes_no) {
|
||||
html += '<div class="flex gap-2 justify-center">' +
|
||||
'<button onclick="stkInputSubmit(\'01\')" class="px-4 py-1 text-sm rounded bg-blue-600 text-white hover:bg-blue-700">' + esc(t('Yes')) + '</button>' +
|
||||
'<button onclick="stkInputSubmit(\'00\')" class="px-4 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">' + esc(t('No')) + '</button>' +
|
||||
'</div>';
|
||||
return html;
|
||||
}
|
||||
const max = Number(pd.max) || 0;
|
||||
const attrs = [
|
||||
'id="stk-input-field"',
|
||||
'class="w-full px-2 py-1 text-sm rounded border border-gray-300 dark:border-slate-600 bg-white dark:bg-slate-900 font-mono"',
|
||||
'type="' + (pd.hidden ? 'password' : 'text') + '"',
|
||||
pd.digits_only ? 'inputmode="numeric"' : '',
|
||||
(max && max !== 0xFF) ? 'maxlength="' + max + '"' : '',
|
||||
pd.immediate ? 'oninput="stkInputImmediate()"' : '',
|
||||
'value="' + esc(pd.default || '') + '"',
|
||||
].filter(Boolean).join(' ');
|
||||
html += '<input ' + attrs + '>';
|
||||
const hint = stkInputHint(pd);
|
||||
if (hint) html += '<div class="text-xs text-gray-400 dark:text-slate-500 mt-1">' + esc(hint) + '</div>';
|
||||
if (pd.help) {
|
||||
html += '<div class="mt-2 text-center"><button onclick="stkMenuRespond(\'help\')" class="text-xs underline text-gray-500 dark:text-slate-400">' + esc(t('Help')) + '</button></div>';
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
// '4–16 characters · digits 0-9 * # + · hidden entry' (pure).
|
||||
function stkInputHint(pd) {
|
||||
pd = pd || {};
|
||||
const parts = [];
|
||||
const lo = Number(pd.min) || 0;
|
||||
const hi = Number(pd.max) || 0;
|
||||
const hasHi = hi && hi !== 0xFF;
|
||||
if (pd.type === 'get_inkey') {
|
||||
parts.push(t('one character'));
|
||||
} else if (lo && hasHi) {
|
||||
parts.push(lo + '\u2013' + hi + ' ' + t('characters'));
|
||||
} else if (hasHi) {
|
||||
parts.push(t('up to') + ' ' + hi + ' ' + t('characters'));
|
||||
} else if (lo) {
|
||||
parts.push(t('At least') + ' ' + lo + ' ' + t('characters'));
|
||||
}
|
||||
if (pd.digits_only || pd.hidden) parts.push(t('digits 0-9 * # +'));
|
||||
if (pd.hidden) parts.push(t('hidden entry'));
|
||||
return parts.join(' \u00b7 ');
|
||||
}
|
||||
|
||||
// Validate the typed answer against the card's request (pure; mirrors the
|
||||
// server rules so the panel can explain before sending).
|
||||
function stkInputValidate(pd, value) {
|
||||
pd = pd || {};
|
||||
const v = value == null ? '' : String(value);
|
||||
const digits = /^[0-9*#+]*$/;
|
||||
if (pd.type === 'get_inkey') {
|
||||
if (v.length !== 1) return { ok: false, error: t('Enter exactly one character') };
|
||||
if (pd.digits_only && !digits.test(v)) return { ok: false, error: t('Digits only (0-9, *, #, +)') };
|
||||
return { ok: true };
|
||||
}
|
||||
const lo = Number(pd.min) || 0;
|
||||
const hi = Number(pd.max) || 0;
|
||||
if (v.length < lo) return { ok: false, error: t('At least') + ' ' + lo + ' ' + t('characters') };
|
||||
if (hi && hi !== 0xFF && v.length > hi) return { ok: false, error: t('At most') + ' ' + hi + ' ' + t('characters') };
|
||||
if ((pd.digits_only || pd.hidden) && !digits.test(v)) return { ok: false, error: t('Digits only (0-9, *, #, +)') };
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
let _stkInputPending = null;
|
||||
|
||||
// Send the typed (or Yes/No) answer; the server validates again and builds
|
||||
// the Text string TERMINAL RESPONSE.
|
||||
async function stkInputSubmit(text) {
|
||||
const pd = _stkInputPending || {};
|
||||
if (text !== '01' && text !== '00') {
|
||||
const field = document.getElementById('stk-input-field');
|
||||
const value = field ? field.value : '';
|
||||
const v = stkInputValidate(pd, value);
|
||||
if (!v.ok) {
|
||||
document.getElementById('stk-menu-panel-content').insertAdjacentHTML('beforeend',
|
||||
'<div class="text-xs text-red-500 mt-1">' + esc(v.error) + '</div>');
|
||||
return;
|
||||
}
|
||||
text = value;
|
||||
}
|
||||
const content = document.getElementById('stk-menu-panel-content');
|
||||
content.innerHTML = '<span class="text-gray-500 text-sm">' + esc(t('Sending...')) + '</span>';
|
||||
try {
|
||||
const data = await pysimFetch('/api/menu-respond', { result: 'ok', text: text });
|
||||
stkMenuHandleResponse(data);
|
||||
} catch (e) {
|
||||
content.innerHTML = '<span class="text-red-500 text-sm">Error: ' + esc(e.message) + '</span>';
|
||||
}
|
||||
}
|
||||
|
||||
// The OK button: submit the pending input, otherwise the plain 'ok' answer.
|
||||
function stkMenuOk() {
|
||||
if (_stkInputPending && (_stkInputPending.type === 'get_input' || _stkInputPending.type === 'get_inkey')) {
|
||||
stkInputSubmit(null);
|
||||
return;
|
||||
}
|
||||
stkMenuRespond('ok');
|
||||
}
|
||||
|
||||
// Immediate digit response (GET INKEY qualifier bit 4): pass the first
|
||||
// character on without asking for confirmation.
|
||||
function stkInputImmediate() {
|
||||
const field = document.getElementById('stk-input-field');
|
||||
if (field && field.value.length >= 1) stkInputSubmit(null);
|
||||
}
|
||||
|
||||
function stkMenuHandleResponse(data) {
|
||||
const content = document.getElementById('stk-menu-panel-content');
|
||||
const backBtn = document.getElementById('stk-back-btn');
|
||||
const btns = document.getElementById('stk-menu-buttons');
|
||||
_stkInputPending = null;
|
||||
btns.classList.add('hidden');
|
||||
if (data.type === 'display_text') {
|
||||
content.innerHTML = '<div class="text-sm whitespace-pre-wrap p-2 bg-gray-50 dark:bg-slate-700 rounded">' + esc(data.text) + '</div>';
|
||||
@@ -9704,6 +9820,12 @@ function stkMenuHandleResponse(data) {
|
||||
backBtn.style.display = '';
|
||||
document.getElementById('stk-ok-btn').style.display = 'none';
|
||||
btns.classList.remove('hidden');
|
||||
} else if (data.type === 'get_input' || data.type === 'get_inkey') {
|
||||
_stkInputPending = data;
|
||||
content.innerHTML = stkInputHtml(data);
|
||||
backBtn.style.display = '';
|
||||
document.getElementById('stk-ok-btn').style.display = data.yes_no ? 'none' : '';
|
||||
btns.classList.remove('hidden');
|
||||
} else {
|
||||
content.innerHTML = '<span class="text-gray-500 text-sm">SW: ' + esc(data.sw || '?') + '</span>';
|
||||
stkSetPillsDisabled(false);
|
||||
@@ -9726,6 +9848,7 @@ async function stkSubItemClick(itemId) {
|
||||
// itself (the menu watchdog's timeout TERMINAL RESPONSE, or a new selection
|
||||
// that shadowed it): the dialog can no longer be answered (v3.6.35).
|
||||
function stkMenuPanelReset() {
|
||||
_stkInputPending = null;
|
||||
const panel = document.getElementById('stk-menu-panel');
|
||||
if (panel) panel.classList.add('hidden');
|
||||
const btns = document.getElementById('stk-menu-buttons');
|
||||
@@ -9734,6 +9857,18 @@ function stkMenuPanelReset() {
|
||||
if (back) back.style.display = 'none';
|
||||
}
|
||||
|
||||
// Open the panel for a pending command the server paused on by itself (a
|
||||
// card-initiated GET INPUT / SELECT ITEM): the reply to our own click renders
|
||||
// through stkMenuHandleResponse instead.
|
||||
function stkMenuShowPending(pd) {
|
||||
document.getElementById('stk-menu-panel-title').textContent = document.getElementById('stk-menu-title').textContent;
|
||||
stkSetPillsDisabled(true);
|
||||
stkMenuStack = [];
|
||||
document.getElementById('stk-back-btn').style.display = 'none';
|
||||
document.getElementById('stk-menu-panel').classList.remove('hidden');
|
||||
stkMenuHandleResponse(pd);
|
||||
}
|
||||
|
||||
// The /api/menu-respond reply for a command the server already answered.
|
||||
function stkNoPendingError(data) {
|
||||
return !!(data && typeof data.error === 'string'
|
||||
@@ -11921,6 +12056,12 @@ function pysimStartBackendPoll() {
|
||||
// A command the server answered by itself must not leave a stale
|
||||
// dialog behind (the panel is not refreshed when the tab is hidden)
|
||||
if (stkChanged && !stk.pending) stkMenuPanelReset();
|
||||
// A command the card sent on its own (not the reply to our click)
|
||||
// opens the panel with its prompt (v3.6.38)
|
||||
if (stkChanged && stk.pending && stk.pending_data && isViewVisible('tab-phone')
|
||||
&& document.getElementById('stk-menu-panel').classList.contains('hidden')) {
|
||||
stkMenuShowPending(stk.pending_data);
|
||||
}
|
||||
if (ts) {
|
||||
const wasRunning = testRunActive();
|
||||
_testRunState = ts;
|
||||
@@ -17497,6 +17638,18 @@ const LANG_RU = {
|
||||
'Event sent.': 'Событие отправлено.',
|
||||
'Event data sent.': 'Данные события отправлены.',
|
||||
'Sending...': 'Отправка...',
|
||||
'Yes': 'Да',
|
||||
'No': 'Нет',
|
||||
'Help': 'Справка',
|
||||
'one character': 'один символ',
|
||||
'characters': 'символов',
|
||||
'up to': 'до',
|
||||
'At least': 'Не менее',
|
||||
'At most': 'Не более',
|
||||
'digits 0-9 * # +': 'цифры 0-9 * # +',
|
||||
'hidden entry': 'скрытый ввод',
|
||||
'Enter exactly one character': 'Введите ровно один символ',
|
||||
'Digits only (0-9, *, #, +)': 'Только цифры (0-9, *, #, +)',
|
||||
'Invalid': 'Некорректное значение',
|
||||
'Service Status': 'Статус сервиса',
|
||||
'Normal service': 'Обычный сервис',
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v310';
|
||||
const CACHE = 'simple-v311';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -26,6 +26,9 @@ code += extractFunc(html, 'stkNoPendingError') + '\n';
|
||||
code += extractFunc(html, 'stkMenuPanelReset') + '\n';
|
||||
code += extractFunc(html, 'stkMenuNaiSuffix') + '\n';
|
||||
code += extractFunc(html, 'stkMenuItemsHtml') + '\n';
|
||||
code += extractFunc(html, 'stkInputHtml') + '\n';
|
||||
code += extractFunc(html, 'stkInputHint') + '\n';
|
||||
code += extractFunc(html, 'stkInputValidate') + '\n';
|
||||
code += 'globalThis.esc = s => s;\n';
|
||||
eval(code);
|
||||
|
||||
@@ -132,3 +135,49 @@ test('an already-answered respond does not reach the dialog handler', async () =
|
||||
assert.strictEqual(calls.handled, null);
|
||||
assert.strictEqual(calls.rendered, 0);
|
||||
});
|
||||
|
||||
test('stkInputHtml builds the card-driven input field', () => {
|
||||
globalThis.t = s => s;
|
||||
const out = stkInputHtml({ type: 'get_input', text: 'PIN', min: 4, max: 16,
|
||||
digits_only: true, hidden: true });
|
||||
assert.ok(out.includes('PIN'), out);
|
||||
assert.ok(out.includes('type="password"'), out); // hidden entry
|
||||
assert.ok(out.includes('inputmode="numeric"'), out); // digits only
|
||||
assert.ok(out.includes('maxlength="16"'), out);
|
||||
assert.ok(out.includes('4\u201316 characters'), out);
|
||||
assert.ok(out.includes('hidden entry'), out);
|
||||
// UCS2 alphabet entry: a plain text field without the numeric keyboard
|
||||
const ucs2 = stkInputHtml({ type: 'get_input', text: 'Name', min: 0, max: 10, ucs2: true });
|
||||
assert.ok(ucs2.includes('type="text"'), ucs2);
|
||||
assert.ok(!ucs2.includes('inputmode'), ucs2);
|
||||
assert.ok(ucs2.includes('up to 10 characters'), ucs2);
|
||||
// 0xFF = no maximum (TS 102 223 8.11): no maxlength and no range hint
|
||||
const noMax = stkInputHtml({ type: 'get_input', text: 'x', min: 0, max: 255 });
|
||||
assert.ok(!noMax.includes('maxlength'), noMax);
|
||||
assert.ok(!noMax.includes('characters'), noMax);
|
||||
// Yes/No: buttons instead of a field
|
||||
const yesNo = stkInputHtml({ type: 'get_inkey', text: 'Continue?', yes_no: true });
|
||||
assert.ok(yesNo.includes("stkInputSubmit('01')"), yesNo);
|
||||
assert.ok(yesNo.includes("stkInputSubmit('00')"), yesNo);
|
||||
assert.ok(!yesNo.includes('stk-input-field'), yesNo);
|
||||
// the card's default text is prefilled; help adds the help action
|
||||
const def = stkInputHtml({ type: 'get_input', text: 'x', default: '1234', help: true });
|
||||
assert.ok(def.includes('value="1234"'), def);
|
||||
assert.ok(def.includes("stkMenuRespond('help')"), def);
|
||||
});
|
||||
|
||||
test('stkInputValidate mirrors the server input rules', () => {
|
||||
globalThis.t = s => s;
|
||||
const pd = { type: 'get_input', min: 4, max: 16, digits_only: true, hidden: true };
|
||||
assert.strictEqual(stkInputValidate(pd, '1234').ok, true);
|
||||
assert.strictEqual(stkInputValidate(pd, '12').ok, false);
|
||||
assert.strictEqual(stkInputValidate(pd, '12345678901234567').ok, false);
|
||||
assert.strictEqual(stkInputValidate(pd, 'abcd').ok, false);
|
||||
// hidden entry allows only the digits set even without digits_only
|
||||
assert.strictEqual(stkInputValidate({ type: 'get_input', hidden: true }, 'ab').ok, false);
|
||||
// GET INKEY: exactly one character, digits-only honours * # +
|
||||
assert.strictEqual(stkInputValidate({ type: 'get_inkey' }, 'a').ok, true);
|
||||
assert.strictEqual(stkInputValidate({ type: 'get_inkey' }, 'ab').ok, false);
|
||||
assert.strictEqual(stkInputValidate({ type: 'get_inkey', digits_only: true }, '*').ok, true);
|
||||
assert.strictEqual(stkInputValidate({ type: 'get_inkey', digits_only: true }, 'a').ok, false);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user