v3.11.0 review fixes: ADM length, TAR re-render hygiene

- the ADM input capped the key at 8 chars, but the server's _verify_adm
  takes 4-16 hex digits (padded to 8 CHV bytes) and the old input had no
  limit; maxlength=16 now, with a static guard in html.test.js.
- cardsTarAdd/cardsTarRemove re-rendered the table from the save-path
  reader, so an emptied role field was silently refilled with its default.
  The renderer no longer invents defaults; a raw reader (cardsTarsFromTable)
  feeds Add/Remove and the language-switch refresh, while the save path
  (cardsTarsFromForm) keeps the default-filling for the mandatory roles.
- the editor's dynamic TAR/keyset rows now rebuild on a language switch
  (refreshDynamicI18n) from what is typed - their placeholders and titles
  carry t() strings.  (The title/Save label were already refreshed there.)
- dropped the now-unused cardsRoleMsl helper and its test references.
- docs: the ADM is documented as 4-16 hex digits (help EN/RU, api.md,
  AGENTS).  sw cache simple-v362.

Tests: 665 Python + 784 frontend green; the whole inline PWA script
syntax-checked.
This commit is contained in:
2026-10-02 10:27:42 +03:00
parent 65c565e058
commit 5a3c4c3529
9 changed files with 63 additions and 31 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ function extractFunc(src, name) {
}
let code = '';
for (const fn of ['cardsTarList', 'cardsRoleDefault', 'cardsRoleTar', 'cardsRoleMsl',
for (const fn of ['cardsTarList', 'cardsRoleDefault', 'cardsRoleTar',
'cardsTarEntry', 'cardsTarMsl', 'cardsApplyFields', 'cardsApply',
'spRefreshFromPreset', 'spPorAccepted', 'spNextCntr', 'spPresetIdx',
'spKeysetKvnOf', 'spKeysetList', 'spKeysetFor', 'spKeysetCheck',
+11 -4
View File
@@ -41,10 +41,10 @@ for (const fn of ['cardsFormValues', 'cardsClearForm', 'cardsEdit',
'cardsKeysetAdd', 'cardsKeysetRemove', 'cardsKeysetKvnUpdate',
'cardsKeysetsFromForm', 'spKeysetSync', 'spKeysetApply', 'spKeysetChanged',
'ramKeysetChanged', 'ramPresetIdx', 'tarPresetIdx', 'spKeysetGuard', 'ramKeysetGuard',
'cardsTarList', 'cardsRoleDefault', 'cardsRoleTar', 'cardsRoleMsl',
'cardsTarList', 'cardsRoleDefault', 'cardsRoleTar',
'cardsTarEntry', 'cardsTarMsl', 'cardsTarText', 'spMslWarningText',
'cardsTarRowHtml', 'cardsTarRowsRender', 'cardsTarAdd', 'cardsTarRemove',
'cardsFreeTarsFromForm', 'cardsTarsFromForm', 'cardsTarsReset',
'cardsFreeTarsFromForm', 'cardsTarsFromTable', 'cardsTarsFromForm', 'cardsTarsReset',
'cardsShowList', 'cardsShowEditor', 'cardsSetFormMode', 'cardsNew',
'cardsCancelEdit']) {
code += extractFunc(html, fn, ASYNC_FNS.indexOf(fn) >= 0) + '\n';
@@ -181,7 +181,6 @@ test('the preset TAR table helpers read the role entries and the MSLs', () => {
{ role: 'usimRfm', tar: 'B00001', msl: '16' },
{ tar: 'AF4D02', msl: '0A', desc: 'applet' }] });
assert.strictEqual(cardsRoleTar(p, 'isd'), 'AF4D01');
assert.strictEqual(cardsRoleMsl(p, 'isd'), '1A');
assert.strictEqual(cardsTarMsl(p, 'af4d02'), '0A');
assert.strictEqual(cardsTarMsl(p, '123456'), '');
assert.strictEqual(cardsTarEntry(p, 'AF4D02').desc, 'applet');
@@ -538,7 +537,7 @@ test('the TAR table renders the role rows and adds/removes free rows', () => {
cardsTarRowsRender([{ tar: 'AF4D01', msl: '0A', desc: 'one' },
{ tar: 'AF4D02', msl: '0A', desc: 'two' }]);
assert.strictEqual(_cardsTarCount, 2);
assert.match(els['cards-tars'].innerHTML, /cards-tr-0-tar"[^>]*value="000000"/);
assert.match(els['cards-tars'].innerHTML, /cards-tr-0-tar"[^>]*value=""/);
assert.match(els['cards-tars'].innerHTML, /ISD compact format/);
assert.match(els['cards-tars'].innerHTML, /cards-tr-3-tar"[^>]*value="AF4D01"/);
assert.match(els['cards-tars'].innerHTML, /cards-tr-4-tar"[^>]*value="AF4D02"/);
@@ -560,6 +559,14 @@ test('the TAR table renders the role rows and adds/removes free rows', () => {
// the role rows are never removable
cardsTarRemove(0);
assert.strictEqual(_cardsTarCount, 2);
// an emptied role field survives a re-render (no silent default refill),
// while the save path still fills the default
tarRow(els, 0, { tar: '', msl: '' });
cardsTarRemove(3);
assert.strictEqual(_cardsTarCount, 0);
assert.match(els['cards-tars'].innerHTML, /cards-tr-0-tar"[^>]*value=""/);
assert.strictEqual(cardsTarsFromForm()[0].tar, '000000');
assert.strictEqual(cardsTarsFromForm()[0].msl, '16');
});
test('the list view opens the editor for a new preset and Cancel returns', () => {
+1 -1
View File
@@ -32,7 +32,7 @@ for (const fn of ['swapNibbles', 'encIccid', 'decIccid',
'cardsNormIccid', 'cardsFindByIccid', 'cardsFindDuplicateIccid',
'cardsIccidFromCard', 'cardsAutoSelectByIccid', 'cardsRender',
'esc', 'spKeysetList', 'spKeysetKvnOf',
'cardsTarList', 'cardsRoleDefault', 'cardsRoleTar', 'cardsRoleMsl',
'cardsTarList', 'cardsRoleDefault', 'cardsRoleTar',
'cardsTarEntry', 'cardsTarMsl', 'cardsTarText', 'cardsScp81Complete']) {
code += extractFunc(html, fn) + '\n';
}
+10
View File
@@ -164,6 +164,16 @@ test('the card editor groups its fields into labelled fieldsets', () => {
assert.ok(!fieldsets.some(f => f.includes('id="cards-name"')));
});
test('the ADM input allows the full 4-16 hex digit key', () => {
// the server's _verify_adm takes 4-16 hex digits (padded to 8 CHV bytes);
// the input must not cap it at the PIN length
const m = /<input id="cards-adm"[^>]*>/.exec(html);
assert.ok(m, 'the ADM input is missing');
const limit = /maxlength="(\d+)"/.exec(m[0]);
assert.ok(!limit || parseInt(limit[1], 10) >= 16,
'the ADM input must allow 4-16 hex digits (server _verify_adm)');
});
test('the Cards tab splits the preset list and the editor into views', () => {
assert.match(html, /id="cards-list-view"/);
assert.match(html, /id="cards-editor-view" class="hidden"/);