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;
+});