diff --git a/frontend/index.html b/frontend/index.html index 0be5f62..31cfc48 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1831,7 +1831,7 @@ function scp80SwitchSubtab(name) { document.getElementById('scp80-sub-ram').classList.toggle('hidden', name !== 'ram'); document.getElementById('scp80-sub-tar').classList.toggle('hidden', name !== 'tar'); if (name === 'ram') ramRender(); - if (name === 'tar') tarProbeRender(); + if (name === 'tar') { tarRender(); tarProbeRender(); } scp80HelpAnchor = { sp: 'secured-packet', ram: 'ram', tar: 'tar-probe' }[name] || 'secured-packet'; setHelpAnchor(scp80HelpAnchor); } @@ -8402,6 +8402,10 @@ function ramRender() { sel.appendChild(opt); }); if (keep !== null && keep !== undefined && cards[keep]) sel.value = String(keep); + // the keyset selector is built from this view's preset: rebuild it now that + // the preset is known (the auto-selection runs before the select has its + // options, which used to leave a stale "not defined in the preset" entry) + spKeysetSync(); ramOpChanged(); ramExpandedDetailsInit(); } @@ -10695,7 +10699,9 @@ function tarRender() { const sel = document.getElementById('tar-card-sel'); if (!sel) return; const prev = parseInt(sel.value, 10); - const keep = (!isNaN(prev) && cards[prev]) ? prev : -1; + // keep the current/remembered selection across rebuilds (the auto-selection + // sets _ramCardIdx before this select has its options) + const keep = (!isNaN(prev) && cards[prev]) ? prev : _ramCardIdx; sel.innerHTML = ''; cards.forEach((c, i) => { const opt = document.createElement('option'); @@ -10703,7 +10709,7 @@ function tarRender() { opt.textContent = c.name || ('Card ' + i); sel.appendChild(opt); }); - if (keep >= 0) sel.value = String(keep); + if (keep !== null && keep !== undefined && cards[keep]) sel.value = String(keep); spKeysetSync(); } @@ -11849,6 +11855,8 @@ function cardsAutoSelectByIccid(iccid) { if (sp) sp.value = String(idx); const ram = document.getElementById('ram-card-sel'); if (ram) ram.value = String(idx); + const tar = document.getElementById('tar-card-sel'); + if (tar) tar.value = String(idx); _ramCardIdx = idx; cardsApply(String(idx)); return idx; diff --git a/frontend/sw.js b/frontend/sw.js index 27321fd..41f0d13 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v338'; +const CACHE = 'simple-v339'; const URLS = [ 'index.html', 'help.html', diff --git a/frontend/tests/cards_iccid.test.js b/frontend/tests/cards_iccid.test.js index 41b35d2..3afaf03 100644 --- a/frontend/tests/cards_iccid.test.js +++ b/frontend/tests/cards_iccid.test.js @@ -34,7 +34,8 @@ const RAW_HEX = '980711090000640090F8'; const DIGITS = '8970119000004600098'; function fakeDoc() { - const els = { 'sp-card-sel': { value: '' }, 'ram-card-sel': { value: '' }, 'cards-iccid': { value: '' } }; + const els = { 'sp-card-sel': { value: '' }, 'ram-card-sel': { value: '' }, + 'tar-card-sel': { value: '' }, 'cards-iccid': { value: '' } }; globalThis.document = { getElementById: id => els[id] || null }; return els; } @@ -78,6 +79,8 @@ test('cardsAutoSelectByIccid selects the preset in both SCP80 views', () => { assert.strictEqual(idx, 0); assert.strictEqual(els['sp-card-sel'].value, '0'); assert.strictEqual(els['ram-card-sel'].value, '0'); + assert.strictEqual(els['tar-card-sel'].value, '0', + 'the TAR probe view gets the equipped card\'s preset too'); assert.deepStrictEqual(applied, ['0']); assert.strictEqual(globalThis._ramCardIdx, 0); }); @@ -162,5 +165,7 @@ test('the From card button and the duplicate refusal are wired into the form', ( test('the card-state update wires the ICCID into the preset selection', () => { assert.ok(html.includes('cardsAutoSelectByIccid(status.iccid);')); assert.ok(html.includes('_cardsAutoIccid = null;')); + assert.ok(html.includes("getElementById('tar-card-sel')"), + 'the auto-selection must cover the TAR probe view'); assert.ok(html.includes("(data.iccid ? ' | ICCID: ' + esc(data.iccid) + '' : '')")); }); diff --git a/frontend/tests/keysets.test.js b/frontend/tests/keysets.test.js index 6874c5c..b561dfe 100644 --- a/frontend/tests/keysets.test.js +++ b/frontend/tests/keysets.test.js @@ -23,7 +23,8 @@ function extractFunc(src, name) { let code = ''; for (const fn of ['spKeysetKvnOf', 'spKeysetList', 'spKeysetFor', 'spKeysetCheck', - 'spKeysetOptionsHtml', 'spCounterTracked', 'esc', 'escHtml']) { + 'spKeysetOptionsHtml', 'spCounterTracked', 'spPresetIdx', 'ramPresetIdx', + 'tarPresetIdx', 'spKeysetSync', 'ramRender', 'esc', 'escHtml']) { code += extractFunc(html, fn) + '\n'; } code += 'function t(s){return s;}\n'; @@ -131,3 +132,46 @@ test('spCounterTracked follows SPI1 b5b4 (TS 102 225 5.1.1)', () => { } assert.strictEqual(spCounterTracked('zz'), false); }); + +test('ramRender rebuilds the keyset selector from the remembered preset', () => { + // regression: the auto-selection runs before the RAM select has options, so + // its keyset list was built with no preset and stayed on "not defined" + const els = { + 'ram-card-sel': { value: '', innerHTML: '', appendChild() {} }, + 'ram-keyset-sel': { value: '', innerHTML: '' }, + 'sp-keyset-sel': { value: '', innerHTML: '' }, + 'tar-keyset-sel': { value: '', innerHTML: '' }, + 'sp-kic-hex': { value: '25' }, + 'sp-kid-hex': { value: '25' }, + }; + globalThis.document = { + getElementById: id => els[id] || null, + createElement: () => ({ value: '', textContent: '' }), + }; + globalThis.cards = [{ name: 'EP', keysets: [ + { kic: '15', kid: '15', kicKey: 'AA', kidKey: 'BB', cntr: '0000000001' }, + { kic: '25', kid: '25', kicKey: 'CC', kidKey: 'DD', cntr: '0000000184' }] }]; + globalThis._ramCardIdx = 0; + globalThis.ramOpChanged = () => {}; + globalThis.ramExpandedDetailsInit = () => {}; + ramRender(); + assert.strictEqual(els['ram-card-sel'].value, '0', + 'the preset select keeps the remembered card'); + assert.match(els['ram-keyset-sel'].innerHTML, /value="2" selected/); + assert.ok(!els['ram-keyset-sel'].innerHTML.includes('not defined'), + 'the keyset selector must not show a stale "not defined" entry'); +}); + +test('the RAM and TAR views rebuild their keyset lists when their preset is known', () => { + // ramRender() syncs the keyset selectors once the preset is set ... + const ram = html.slice(html.indexOf('function ramRender()'), + html.indexOf('function ramRender()') + 1400); + assert.ok(ram.includes('spKeysetSync();'), 'ramRender must rebuild the keyset lists'); + // ... tarRender() already does, and keeps the remembered selection + const tar = html.slice(html.indexOf('function tarRender()'), + html.indexOf('function tarRender()') + 1200); + assert.ok(tar.includes('spKeysetSync();')); + assert.ok(tar.includes(': _ramCardIdx;'), 'tarRender keeps the remembered preset'); + // opening the TAR pill populates both its selectors + assert.ok(html.includes("if (name === 'tar') { tarRender(); tarProbeRender(); }")); +}); diff --git a/frontend/tests/ram.test.js b/frontend/tests/ram.test.js index 6ba8e37..135735c 100644 --- a/frontend/tests/ram.test.js +++ b/frontend/tests/ram.test.js @@ -28,6 +28,7 @@ const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamB 'ramExpandedReport', 'ramExpandedTags', 'ramExpandedGroups', 'ramExpandedElfForm', 'ramElfVersionHint', 'ramChainGetResponse', 'ramDeriveElfVersions', 'ramElfAppletCandidate', 'spCntrLow', 'ramCntrLowHtml', 'ramCntrLowPresetIdx', 'ramShowCntrLow', 'spKeysetKvnOf', 'spKeysetList', 'spKeysetFor', 'spKeysetCheck', + 'spKeysetOptionsHtml', 'spPresetIdx', 'tarPresetIdx', 'cardsKeysetsFromForm', 'spKeysetSync', 'spKeysetApply', 'spKeysetChanged', 'ramKeysetChanged', 'ramPresetIdx', 'spKeysetGuard', 'ramKeysetGuard', 'ramCardIdxAfterRemove', 'ramClearResults', 'ramHideProgress', 'ramOpChanged', 'ramRender', 'ramApplyCard', 'ramExecute', 'decodePrivileges', 'ramActionBtn', 'ramCapToolkitMode', 'ramOpProgressText',