Improved error feedbacks

This commit is contained in:
Pierre committed 2026-10-04 12:39:38 +02:00
1 parent ece851f5a0
commit cfb5334666
12 files changed
+253 -13

No files matched your search

@@ -60,3 +60,24 @@ test('groups plain app.log lines between explicit initialization markers',()=>{
assert.equal(grouped[1].group_inferred,true);
assert.equal(grouped[2].group_inferred,undefined);
});
test('clears configured live files, keeps imported archives, and updates the log error count',()=>{
const counts=[];const e=new LogExplorer({},count=>counts.push(count));
messages=[];const webview={postMessage:m=>messages.push(m)};e.panel={webview};e.activeWebview=webview;
const errorFile=path.join(root,'logs/error.log');
const archive=path.join(root,'logs/history.log.1');
fs.writeFileSync(file,'27-09-26 10:12:13 ERROR app: failed\n');
fs.writeFileSync(errorFile,'27-09-26 10:12:13 ERROR app: failed\n');
fs.writeFileSync(archive,'27-09-26 10:12:14 INFO app: archived\n');
e.imported.add(archive);e.poll();
assert.equal(counts.at(-1),1);
e.clearLogs();
assert.equal(fs.statSync(file).size,0);
assert.equal(fs.statSync(errorFile).size,0);
assert.match(fs.readFileSync(archive,'utf8'),/archived/);
assert.equal(counts.at(-1),0);
assert.deepEqual(messages.filter(message=>message.type==='entries').at(-1).entries,[]);
fs.appendFileSync(file,'27-09-26 10:12:15 CRITICAL app: new failure\n');e.poll();
assert.equal(counts.at(-1),1);
assert.equal(latest()[0].message,'new failure');
});
+17
View File
@@ -0,0 +1,17 @@
const {test}=require('node:test');
const assert=require('node:assert/strict');
const {findLogSource}=require('../out/logSource');
const expression='sensor.target_temperature_heatpump + 0.5';
const entry={app:'living_room_heatpump',message:`Error parsing string "${expression}" at character 0:Unknown variable : sensor.target_temperature_heatpump`};
test('resolves the failing expression inside the matching app rather than another app',()=>{
const docs=[{path:'/conf/apps/heating.yaml',text:`bedroom_heatpump:\n target_temperature: ${expression}\nliving_room_heatpump:\n target_temperature: ${expression}\n`}];
assert.deepEqual(findLogSource(entry,docs),{path:'/conf/apps/heating.yaml',line:4});
});
test('resolves a unique expression in a shared template and ignores comments',()=>{
assert.deepEqual(findLogSource(entry,[{path:'/conf/apps/templates.yml',text:`# ${expression}\nlibrary:\n value: ${expression}`}]),{path:'/conf/apps/templates.yml',line:3});
});
test('does not guess when a source is missing or ambiguous',()=>{
assert.equal(findLogSource(entry,[]),undefined);
assert.equal(findLogSource(entry,[{path:'a.yaml',text:`living_room_heatpump:\n a: ${expression}\n b: ${expression}`}]),undefined);
assert.equal(findLogSource({...entry,message:'Initialization failed'},[]),undefined);
});
+54
View File
@@ -157,3 +157,57 @@ test('shows consecutive logs from one explicit group as a single visible row',()
assert.doesNotMatch(rows[0].textContent,/\[group=/);
dom.window.close();
});
test('global error counter shows all loaded errors and opens them despite active filters',()=>{
const {dom,d,entries,send}=setup();
send([...entries,{...entries[3],event_id:'alpha-error',level:'ERROR',message:'Alpha failed'},
{...entries[4],event_id:'beta-critical',level:'CRITICAL',message:'Beta failed'},
{...entries[4],event_id:'supplement',kind:'evaluation',level:'ERROR',message:'Supplemental'}]);
const counter=d.getElementById('error-count');
assert.equal(counter.textContent,'2 errors');assert.ok(counter.classList.contains('has-errors'));
d.getElementById('search').value='nothing';d.getElementById('period').value='15';
counter.click();
assert.equal(d.getElementById('search').value,'');assert.equal(d.getElementById('period').value,'0');
assert.equal(d.getElementById('level').value,'ERROR');
assert.equal(d.querySelectorAll('#apps .row').length,2);
assert.match(d.getElementById('detail').textContent,/failed/);
dom.window.close();
});
test('exits errors with the counter or All levels and restores normal app sorting',()=>{
const {dom,d,entries,send}=setup();
send([...entries,{...entries[3],event_id:'old-alpha-error',time:entries[0].time,level:'ERROR',message:'Old error'}]);
const counter=d.getElementById('error-count');
counter.click();assert.equal(counter.getAttribute('aria-pressed'),'true');
assert.equal(d.querySelector('[data-app]').dataset.app,'alpha');
counter.click();
assert.equal(d.getElementById('level').value,'all');
assert.equal(counter.getAttribute('aria-pressed'),'false');
assert.equal(d.querySelector('[data-app]').dataset.app,'beta');
counter.click();
d.getElementById('level').value='all';d.getElementById('level').dispatchEvent(new dom.window.Event('input'));
assert.equal(d.querySelector('[data-app]').dataset.app,'beta');
assert.equal(d.querySelectorAll('.row.selected').length,0);
assert.equal(counter.getAttribute('aria-pressed'),'false');
dom.window.close();
});
test('Open source in a group targets the parsing error even when the last entry has no source',()=>{
const {dom,d,entries,send,sent}=setup();
send([{...entries[0],event_id:'parse-error',level:'ERROR',source:{path:'/conf/apps/heating.yaml',line:160}},
{...entries[1],event_id:'init-failed',level:'ERROR',message:'Initialization failed'}]);
const app=d.querySelector('[data-app="alpha"]');app.open=true;app.dispatchEvent(new dom.window.Event('toggle'));
app.querySelector('.row').click();
const source=[...d.querySelectorAll('#detail button')].find(button=>button.textContent==='Open source');
assert.ok(source);source.click();assert.equal(sent.at(-1).type,'source');assert.equal(sent.at(-1).id,'parse-error');
dom.window.close();
});
test('Clear logs sends a request and resets its button after completion',()=>{
const {dom,d,sent}=setup();
const button=d.getElementById('clear');button.click();
assert.equal(sent.at(-1).type,'clear');assert.equal(button.disabled,true);
dom.window.dispatchEvent(new dom.window.MessageEvent('message',{data:{type:'clear-result',cleared:2,failures:[]}}));
assert.equal(button.disabled,false);assert.equal(button.textContent,'Clear logs');
dom.window.close();
});