ui: label comparison mismatches with snapshot names
Snapshot comparison results now use the master/checked snapshot names
where profile checks keep expected/actual: the raw-data field rows, the
generic mismatch line and the decoded FCI comparison table headers
(profilerRenderReport/fcpDiffHtml take an optional {expected, actual}
labels object; the name column widens to w-40 and wraps). Labels are
stored with the results so the language switch and the Only mismatches
filter re-render correctly; Check card snapshot and live checks are
unchanged. Tests for both labeled and default rendering; help/README
updated; SW cache v101 -> v102.
This commit is contained in:
@@ -400,7 +400,7 @@
|
||||
<li><strong>Импорт снимка</strong> — загружает снимок из JSON-файла.</li>
|
||||
<li>В каждой строке снимка — <strong>Открыть</strong>, <strong>Экспорт</strong> и <strong>Удалить</strong>. <strong>Открыть</strong> показывает все захваченные данные только для чтения (сырой FCI с декодированным FCI, содержимое); редактируется только имя снимка.</li>
|
||||
<li><strong>Проверить снимок карты</strong> в строке профиля выполняет правила профиля на выбранном из списка снимке, без картридера. Отчёт такой же, как при проверке карты; файлы, содержимое которых не было захвачено при сканировании, помечаются как непроверяемые ошибки.</li>
|
||||
<li><strong>Сравнить снимки</strong> сравнивает два снимка без картридера так же, как проверка профиля: выберите <em>эталонный</em> снимок и <em>снимок для проверки</em>, при необходимости включите маску первых 4 байт EF.IMSI/EF.ICCID (включена по умолчанию) и получите такой же отчёт. Файлы, которые есть только в проверяемом снимке, помечаются как лишние. «К списку» возвращает на вкладку «Снимки карт».</li>
|
||||
<li><strong>Сравнить снимки</strong> сравнивает два снимка без картридера так же, как проверка профиля: выберите <em>эталонный</em> снимок и <em>снимок для проверки</em>, при необходимости включите маску первых 4 байт EF.IMSI/EF.ICCID (включена по умолчанию) и получите такой же отчёт; в этом отчёте поля расхождений и колонки сравнения FCI подписаны именами эталонного и проверяемого снимков вместо expected/actual. Файлы, которые есть только в проверяемом снимке, помечаются как лишние. «К списку» возвращает на вкладку «Снимки карт».</li>
|
||||
</ul>
|
||||
|
||||
</section>
|
||||
|
||||
+1
-1
@@ -400,7 +400,7 @@
|
||||
<li><strong>Import snapshot</strong> — loads a snapshot from a JSON file.</li>
|
||||
<li>Each snapshot row has <strong>Open</strong>, <strong>Export</strong>, and <strong>Delete</strong>. <strong>Open</strong> shows all captured data read-only (raw FCI with the decoded FCI, contents); only the snapshot name is editable.</li>
|
||||
<li><strong>Check card snapshot</strong> on a profile row runs the profile rules against a snapshot you pick from the list, without a card reader. The report is the same as a live check; files whose contents were not captured during the scan are reported as unverifiable errors.</li>
|
||||
<li><strong>Compare snapshots</strong> compares two snapshots offline, exactly like a profile check: pick the <em>master</em> snapshot and the <em>snapshot to check</em>, optionally masking the first 4 bytes of EF.IMSI/EF.ICCID (on by default), and get the same pass/fail report. Files present only in the checked snapshot are reported as extra files. Back to list returns to the Card snapshots tab.</li>
|
||||
<li><strong>Compare snapshots</strong> compares two snapshots offline, exactly like a profile check: pick the <em>master</em> snapshot and the <em>snapshot to check</em>, optionally masking the first 4 bytes of EF.IMSI/EF.ICCID (on by default), and get the same pass/fail report; in that report the mismatch fields and the FCI comparison columns are labeled with the master and checked snapshot names instead of expected/actual. Files present only in the checked snapshot are reported as extra files. Back to list returns to the Card snapshots tab.</li>
|
||||
</ul>
|
||||
|
||||
</section>
|
||||
|
||||
+19
-11
@@ -7089,6 +7089,7 @@ let profilerView = 'list';
|
||||
let profilerEditId = null;
|
||||
let profilerDraft = null;
|
||||
let profilerResults = null;
|
||||
let profilerResultsLabels = null;
|
||||
let profilerMismatchOnly = false;
|
||||
let snapshots = [];
|
||||
let snapshotViewId = null;
|
||||
@@ -8113,7 +8114,7 @@ async function profilerCheck(i) {
|
||||
await profilerRunProfile(p, null, p.name);
|
||||
}
|
||||
|
||||
async function profilerRunProfile(p, source, title, extraResults) {
|
||||
async function profilerRunProfile(p, source, title, extraResults, labels) {
|
||||
profilerSetView('results');
|
||||
document.getElementById('profiler-results-title').textContent = title;
|
||||
const prog = document.getElementById('profiler-progress');
|
||||
@@ -8128,6 +8129,7 @@ async function profilerRunProfile(p, source, title, extraResults) {
|
||||
if (extraResults && extraResults.length) results.push(...extraResults);
|
||||
prog.textContent = '';
|
||||
profilerResults = results;
|
||||
profilerResultsLabels = labels || null;
|
||||
profilerRenderResultsView();
|
||||
}
|
||||
|
||||
@@ -8238,7 +8240,8 @@ async function snapshotCompareRun() {
|
||||
});
|
||||
document.getElementById('snapshot-compare-modal').classList.add('hidden');
|
||||
const pseudo = { name: master.name + ' → ' + check.name, rules: profilerRulesFromSnapshot(master, maskFids) };
|
||||
await profilerRunProfile(pseudo, profilerSnapshotSource(check), pseudo.name, profilerExtraFileResults(master, check));
|
||||
await profilerRunProfile(pseudo, profilerSnapshotSource(check), pseudo.name, profilerExtraFileResults(master, check),
|
||||
{ expected: master.name, actual: check.name });
|
||||
}
|
||||
|
||||
function profilerToggleMismatchOnly(checked) {
|
||||
@@ -8263,7 +8266,7 @@ function profilerRenderResultsView() {
|
||||
' · <span class="text-yellow-600 font-semibold">' + errors + ' ' + t('errors') + '</span>';
|
||||
const shown = profilerVisibleResults(profilerResults, profilerMismatchOnly);
|
||||
document.getElementById('profiler-report').innerHTML = shown.length
|
||||
? profilerRenderReport(shown)
|
||||
? profilerRenderReport(shown, profilerResultsLabels)
|
||||
: '<div class="text-sm text-emerald-600">' + esc(t('No mismatches')) + '</div>';
|
||||
}
|
||||
|
||||
@@ -8684,7 +8687,9 @@ function fcpDecode(hex) {
|
||||
return { ok: !error && items.length > 0, template, items, error };
|
||||
}
|
||||
|
||||
function fcpDiffHtml(expectedHex, actualHex) {
|
||||
function fcpDiffHtml(expectedHex, actualHex, labels) {
|
||||
const expLabel = (labels && labels.expected) ? labels.expected : t('expected');
|
||||
const actLabel = (labels && labels.actual) ? labels.actual : t('actual');
|
||||
const e = fcpDecode(expectedHex);
|
||||
const a = fcpDecode(actualHex);
|
||||
const eBad = !!e.error, aBad = !!a.error;
|
||||
@@ -8700,8 +8705,8 @@ function fcpDiffHtml(expectedHex, actualHex) {
|
||||
html += '<table class="w-full text-xs border-collapse">';
|
||||
html += '<thead><tr class="border-b border-gray-200 dark:border-slate-700">' +
|
||||
'<th class="text-left py-0.5 px-1 font-medium text-gray-500 dark:text-slate-400">' + esc(t('Parameter')) + '</th>' +
|
||||
'<th class="text-left py-0.5 px-1 font-medium text-gray-500 dark:text-slate-400">' + esc(t('expected')) + '</th>' +
|
||||
'<th class="text-left py-0.5 px-1 font-medium text-gray-500 dark:text-slate-400">' + esc(t('actual')) + '</th></tr></thead><tbody>';
|
||||
'<th class="text-left py-0.5 px-1 font-medium text-gray-500 dark:text-slate-400">' + esc(expLabel) + '</th>' +
|
||||
'<th class="text-left py-0.5 px-1 font-medium text-gray-500 dark:text-slate-400">' + esc(actLabel) + '</th></tr></thead><tbody>';
|
||||
for (const k of keys) {
|
||||
const ev = eMap[k], av = aMap[k];
|
||||
const ed = ev ? (ev.decoded === null ? '' : (ev.decoded || ev.value)) : '—';
|
||||
@@ -8744,7 +8749,10 @@ function profilerFciInput(i, value) {
|
||||
profilerUpdateFciPreview(i, value);
|
||||
}
|
||||
|
||||
function profilerRenderReport(results) {
|
||||
function profilerRenderReport(results, labels) {
|
||||
const expLabel = (labels && labels.expected) ? labels.expected : t('expected');
|
||||
const actLabel = (labels && labels.actual) ? labels.actual : t('actual');
|
||||
const labelCls = 'text-xs text-gray-500 dark:text-slate-400 text-right shrink-0 ' + (labels ? 'w-40 break-all' : 'w-24');
|
||||
let html = '';
|
||||
for (const r of results) {
|
||||
const color = r.status === 'pass' ? 'text-emerald-600' : (r.status === 'fail' ? 'text-red-600' : 'text-yellow-600');
|
||||
@@ -8768,15 +8776,15 @@ function profilerRenderReport(results) {
|
||||
html += '<div class="mt-1">';
|
||||
html += '<div class="text-xs text-red-600 font-mono pl-2">' + esc(c.label) + '</div>';
|
||||
html += '<div class="flex items-center gap-2 mt-0.5 pl-2">' +
|
||||
'<span class="text-xs text-gray-500 dark:text-slate-400 w-24 text-right shrink-0">' + esc(t('expected')) + '</span>' +
|
||||
'<span class="' + labelCls + '">' + esc(expLabel) + '</span>' +
|
||||
'<input readonly title="' + escHtml(String(c.expected)) + '" value="' + escHtml(String(c.expected)) + '" class="flex-1 min-w-0 font-mono text-xs border border-gray-300 dark:border-slate-600 rounded px-2 py-1 bg-gray-100 dark:bg-slate-800"></div>';
|
||||
html += '<div class="flex items-center gap-2 mt-0.5 pl-2">' +
|
||||
'<span class="text-xs text-gray-500 dark:text-slate-400 w-24 text-right shrink-0">' + esc(t('actual')) + '</span>' +
|
||||
'<span class="' + labelCls + '">' + esc(actLabel) + '</span>' +
|
||||
'<input readonly title="' + escHtml(String(c.actual)) + '" value="' + escHtml(String(c.actual)) + '" class="flex-1 min-w-0 font-mono text-xs border border-gray-300 dark:border-slate-600 rounded px-2 py-1 bg-gray-100 dark:bg-slate-800"></div>';
|
||||
if (c.label === 'fci') html += fcpDiffHtml(String(c.expected), String(c.actual));
|
||||
if (c.label === 'fci') html += fcpDiffHtml(String(c.expected), String(c.actual), labels);
|
||||
html += '</div>';
|
||||
} else {
|
||||
html += '<div class="text-xs text-red-600 mt-1">' + esc(c.label) + ': ' + esc(t('expected')) + ' <b>' + esc(String(c.expected)) + '</b>, ' + esc(t('actual')) + ' <b>' + esc(String(c.actual)) + '</b></div>';
|
||||
html += '<div class="text-xs text-red-600 mt-1">' + esc(c.label) + ': ' + esc(expLabel) + ' <b>' + esc(String(c.expected)) + '</b>, ' + esc(actLabel) + ' <b>' + esc(String(c.actual)) + '</b></div>';
|
||||
}
|
||||
}
|
||||
const recFailed = r.recordsMismatch || (r.checks || []).some(c => /^content\.rec\d+$/.test(c.label) && !c.ok);
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'otaman-v101';
|
||||
const CACHE = 'otaman-v102';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -584,7 +584,7 @@ test('profilerRenderReport renders raw-data mismatches as aligned readonly field
|
||||
assert.ok(html.includes('font-mono'));
|
||||
assert.ok(html.includes('value="621082024021"'));
|
||||
assert.ok(html.includes('value="621082024022"'));
|
||||
assert.ok(html.includes('w-24 text-right'));
|
||||
assert.ok(html.includes('text-right shrink-0 w-24'));
|
||||
// non-raw check stays inline
|
||||
assert.ok(html.includes('content.records: expected'));
|
||||
assert.ok(!fciBlock.includes(': expected'));
|
||||
@@ -1227,3 +1227,42 @@ test('profilerSnapshotCountLabel appends the scan time when available', () => {
|
||||
assert.strictEqual(empty, '0 files');
|
||||
delete global.t;
|
||||
});
|
||||
|
||||
test('profilerRenderReport labels mismatches with custom names', () => {
|
||||
global.t = s => s;
|
||||
global.pysimCustomFiles = [];
|
||||
const html = profilerRenderReport([{
|
||||
path: 'MF/6F3A', name: 'EF.ADN', status: 'fail',
|
||||
checks: [
|
||||
{ label: 'content', expected: 'AABB', actual: 'CCDD', ok: false },
|
||||
{ label: 'fileSize', expected: 4, actual: 9, ok: false },
|
||||
],
|
||||
}], { expected: 'Master snap', actual: 'Check snap' });
|
||||
assert.ok(html.includes('Master snap'), html);
|
||||
assert.ok(html.includes('Check snap'), html);
|
||||
assert.ok(!html.includes('>expected<'), html);
|
||||
assert.ok(!html.includes('>actual<'), html);
|
||||
delete global.t;
|
||||
delete global.pysimCustomFiles;
|
||||
});
|
||||
|
||||
test('profilerRenderReport keeps expected/actual without labels', () => {
|
||||
global.t = s => s;
|
||||
global.pysimCustomFiles = [];
|
||||
const html = profilerRenderReport([{
|
||||
path: 'MF/6F3A', status: 'fail',
|
||||
checks: [{ label: 'fileSize', expected: 4, actual: 9, ok: false }],
|
||||
}]);
|
||||
assert.ok(html.includes(' expected '), html);
|
||||
assert.ok(html.includes(' actual '), html);
|
||||
delete global.t;
|
||||
delete global.pysimCustomFiles;
|
||||
});
|
||||
|
||||
test('fcpDiffHtml headers use custom labels', () => {
|
||||
global.t = s => s;
|
||||
const diff = fcpDiffHtml(FCP_TRANSPARENT, '62128002000A8202412183026F078A0105880110', { expected: 'Master', actual: 'Candidate' });
|
||||
assert.ok(diff.includes('>Master<'), diff);
|
||||
assert.ok(diff.includes('>Candidate<'), diff);
|
||||
delete global.t;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user