From dbf9e8559b8f5fec367aa0ad3b97d7ce6ed379e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Thu, 10 Sep 2026 23:14:50 +0300 Subject: [PATCH] profiler: show raw-data mismatches as aligned monospace fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FCI and content (incl. record) mismatches are now rendered as two read-only monospace inputs — expected on top, actual directly beneath — sharing a fixed-width right-aligned label column (w-24) so both fields start at exactly the same horizontal position and stretch to fill the row (flex-1/min-w-0, no overlap). A title attribute shows the full value on hover. Non-raw checks (fileSize, recordLen, numRecords, content.records, read failure) keep the inline line. New profilerRawDataCheck helper; 2 new tests. SW cache v59 -> v60. --- frontend/index.html | 22 ++++++++++++++++++++- frontend/sw.js | 2 +- frontend/tests/profiler.test.js | 35 ++++++++++++++++++++++++++++++++- 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index 3ddd1f8..c8d43e4 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -7737,6 +7737,14 @@ function profilerNumRanges(nums) { return parts.join(', '); } +// Checks whose expected/actual are raw data (hex), rendered as aligned fields. +function profilerRawDataCheck(c) { + if (c.label === 'fci') return true; + if (/^content\.rec\d+$/.test(c.label)) return true; + if (c.label === 'content') return c.expected !== 'readable'; + return false; +} + function profilerRenderReport(results) { let html = ''; for (const r of results) { @@ -7755,7 +7763,19 @@ function profilerRenderReport(results) { if (r.error) html += '
' + esc(r.error) + '
'; for (const c of r.checks) { if (c.ok) continue; - html += '
' + esc(c.label) + ': ' + esc(t('expected')) + ' ' + esc(String(c.expected)) + ', ' + esc(t('actual')) + ' ' + esc(String(c.actual)) + '
'; + if (profilerRawDataCheck(c)) { + html += '
'; + html += '
' + esc(c.label) + '
'; + html += '
' + + '' + esc(t('expected')) + '' + + '
'; + html += '
' + + '' + esc(t('actual')) + '' + + '
'; + html += '
'; + } else { + html += '
' + esc(c.label) + ': ' + esc(t('expected')) + ' ' + esc(String(c.expected)) + ', ' + esc(t('actual')) + ' ' + esc(String(c.actual)) + '
'; + } } const recFailed = (r.checks || []).some(c => /^content\.rec\d+$/.test(c.label) && !c.ok); if (recFailed && r.recordsMatched && r.recordsMatched.length) { diff --git a/frontend/sw.js b/frontend/sw.js index c21ade2..05d804f 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'otaman-v59'; +const CACHE = 'otaman-v60'; const URLS = [ 'index.html', 'help.html', diff --git a/frontend/tests/profiler.test.js b/frontend/tests/profiler.test.js index 29a3fbb..4442bd1 100644 --- a/frontend/tests/profiler.test.js +++ b/frontend/tests/profiler.test.js @@ -21,7 +21,7 @@ function extractFunc(src, name, asyncFn) { return (asyncFn ? 'async ' : '') + src.slice(m.index, i + 1); } -const FNS = ['profilerNormHex', 'profilerNormHexStrict', 'profilerMatch', 'profilerMatchMin', 'profilerMaskPrefix4', 'profilerFileFields', 'profilerContentKindForFileType', 'profilerEmptyRecordContent', 'profilerValidateProfile', 'profilerCustomNameForPath', 'profilerUpdateRulePath', 'profilerResultAspects', 'profilerAspectSummary', 'profilerNumRanges', 'esc', 'profilerRenderReport']; +const FNS = ['profilerNormHex', 'profilerNormHexStrict', 'profilerMatch', 'profilerMatchMin', 'profilerMaskPrefix4', 'profilerFileFields', 'profilerContentKindForFileType', 'profilerEmptyRecordContent', 'profilerValidateProfile', 'profilerCustomNameForPath', 'profilerUpdateRulePath', 'profilerResultAspects', 'profilerAspectSummary', 'profilerNumRanges', 'esc', 'escHtml', 'profilerRawDataCheck', 'profilerRenderReport']; let code = ''; for (const f of FNS) code += extractFunc(html, f) + '\n'; code += extractFunc(html, 'profilerBuildFileRule', true) + '\n'; @@ -535,3 +535,36 @@ test('profilerRenderReport includes the checked-aspects summary and matching-rec assert.ok(html.includes('1-2, 5')); delete global.t; }); + +test('profilerRawDataCheck identifies raw-data checks', () => { + assert.strictEqual(profilerRawDataCheck({ label: 'fci' }), true); + assert.strictEqual(profilerRawDataCheck({ label: 'content', expected: 'AABBCC' }), true); + assert.strictEqual(profilerRawDataCheck({ label: 'content.rec3' }), true); + assert.strictEqual(profilerRawDataCheck({ label: 'content', expected: 'readable' }), false); + assert.strictEqual(profilerRawDataCheck({ label: 'content.records' }), false); + assert.strictEqual(profilerRawDataCheck({ label: 'fileSize' }), false); +}); + +test('profilerRenderReport renders raw-data mismatches as aligned readonly fields', () => { + global.t = s => s; + global.pysimCustomFiles = []; + const html = profilerRenderReport([ + { path: 'MF/6F07', name: 'EF.IMSI', status: 'fail', checks: [ + { label: 'fci', ok: false, expected: '621082024021', actual: '621082024022' }, + ] }, + { path: 'MF/7F20/6F3A', name: 'EF.X', status: 'fail', checks: [ + { label: 'content.records', ok: false, expected: '30 records', actual: '10 records' }, + ] }, + ]); + const fciBlock = html.split('').find(s => s.includes('621082024021')); + assert.ok(html.includes('readonly')); + assert.ok((html.match(/readonly/g) || []).length >= 2); + 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')); + // non-raw check stays inline + assert.ok(html.includes('content.records: expected')); + assert.ok(!fciBlock.includes(': expected')); + delete global.t; +});