diff --git a/frontend/index.html b/frontend/index.html index 9755398..132f3f8 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -5940,14 +5940,10 @@ async function pysimFsRefresh() { async function pysimFsLoadChildren(node) { if (node.children) return; try { - let body = { name: node.name, fid: node.fid }; + const body = { name: node.name, fid: node.fid }; const parentSel = getParentSel(node); if (parentSel) body.parent_sel = parentSel; - let data = await pysimFetch('/api/tree', body); - if (!data || data.exists === false) { - body = { name: node.name, fid: node.fid }; - data = await pysimFetch('/api/tree', body); - } + const data = await pysimFetch('/api/tree', body); if (!data || data.exists === false || data.success === false || data.error) { throw new Error((data && data.error) || 'File not found'); } @@ -5965,7 +5961,6 @@ async function pysimFsLoadChildren(node) { pysimCustomInject(node); } catch (e) { node.exists = false; - node.children = []; pysimFsRenderTree(); } } @@ -6007,7 +6002,7 @@ async function pysimFsProbeAll() { if (probe.stop) return; if (!child.isDir) continue; await pysimFsLoadChildren(child); - const exists = child.exists !== false; + const exists = child.exists === true; probe.dirs++; count(exists); pysimFsRenderTree(); @@ -6019,7 +6014,7 @@ async function pysimFsProbeAll() { const files = []; const collect = node => { for (const child of (node.children || [])) { - if (child.isDir) { if (child.exists !== false) collect(child); } + if (child.isDir) { if (child.exists === true) collect(child); } else files.push(child); } }; @@ -6031,7 +6026,6 @@ async function pysimFsProbeAll() { const data = await pysimFetch('/api/select', pysimFsSelectBody(file)); const exists = !!(data && data.exists !== false && !data.error); file.exists = exists; - if (!exists) file.children = []; count(exists); probe.done++; pysimFsProbeUi(t('Probing:') + ' ' + probe.done + '/' + probe.total, true); diff --git a/frontend/sw.js b/frontend/sw.js index aa2a56f..3939048 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'otaman-v116'; +const CACHE = 'otaman-v117'; const URLS = [ 'index.html', 'help.html', diff --git a/frontend/tests/fs_load.test.js b/frontend/tests/fs_load.test.js index 408f5a1..9ead305 100644 --- a/frontend/tests/fs_load.test.js +++ b/frontend/tests/fs_load.test.js @@ -54,9 +54,9 @@ test('tree error payload marks the directory as absent', async () => { ]; await pysimFsLoadChildren(node); assert.strictEqual(node.exists, false); - assert.deepStrictEqual(node.children, []); + assert.strictEqual(node.children, null); assert.strictEqual(renders, 1); - assert.strictEqual(calls.length, 2); + assert.strictEqual(calls.length, 1); assert.strictEqual(calls[0].body.parent_sel, 'MF'); }); @@ -68,23 +68,28 @@ test('error payload without exists is not treated as an empty listing', async () ]; await pysimFsLoadChildren(node); assert.strictEqual(node.exists, false); - assert.deepStrictEqual(node.children, []); + assert.strictEqual(node.children, null); assert.strictEqual(renders, 1); assert.strictEqual(calls.length, 1); }); -test('retry without parent_sel succeeds and maps children', async () => { +test('a 200 exists:false response marks the directory absent without retrying', async () => { const node = setup(); - responses = [ - { exists: false }, - { exists: true, children: [{ name: 'EF.IMSI', fid: '6f07', isDir: false }] }, - ]; + responses = [{ exists: false }]; + await pysimFsLoadChildren(node); + assert.strictEqual(node.exists, false); + assert.strictEqual(node.children, null); + assert.strictEqual(calls.length, 1); +}); + +test('a node with loaded children is not fetched again', async () => { + const node = setup(); + node.exists = true; + node.children = [{ name: 'EF.UPLMNWLAN', fid: '4f42', isDir: false, exists: true }]; await pysimFsLoadChildren(node); assert.strictEqual(node.exists, true); assert.strictEqual(node.children.length, 1); - assert.strictEqual(node.children[0].parent, node); - assert.strictEqual(node.children[0].exists, true); - assert.strictEqual(calls[1].body.parent_sel, undefined); + assert.strictEqual(calls.length, 0); }); test('empty successful listing keeps the directory present', async () => { diff --git a/frontend/tests/fs_probe.test.js b/frontend/tests/fs_probe.test.js index a44ff86..1cd100a 100644 --- a/frontend/tests/fs_probe.test.js +++ b/frontend/tests/fs_probe.test.js @@ -104,7 +104,7 @@ test('an absent directory is marked and its subtree is never fetched', async () await pysimFsProbeAll(); assert.strictEqual(pysimFsTreeRoot.children[0].exists, false); assert.deepStrictEqual(selectNames(), ['EF.ROOT']); - assert.strictEqual(calls.filter(c => c.path === '/api/tree' && c.body.name === 'DF.B').length, 2); + assert.strictEqual(calls.filter(c => c.path === '/api/tree' && c.body.name === 'DF.B').length, 1); const status = els['pysim-fs-probe-status'].textContent; assert.match(status, /2\/2 files/); assert.match(status, /0 present/); @@ -122,3 +122,22 @@ test('stop halts the walk and still reports a summary', async () => { assert.ok(status.startsWith('Stopped —'), status); assert.strictEqual(els['pysim-fs-probe-btn'].textContent, 'Probe all files'); }); + +test('children of an absent directory are neither fetched nor selected', async () => { + const stale = Object.assign(df('DF.C', '5f03'), { + exists: false, + children: [{ name: 'EF.STALE', fid: '6f0e', isDir: false, exists: true, children: null }], + }); + root([stale, ef('EF.ROOT', '2f01')]); + setup([ + { path: '/api/select', name: 'EF.ROOT', reply: { exists: true } }, + ]); + await pysimFsProbeAll(); + assert.deepStrictEqual(selectNames(), ['EF.ROOT']); + assert.strictEqual(stale.exists, false); + assert.strictEqual(calls.filter(c => c.path === '/api/tree').length, 0); + const status = els['pysim-fs-probe-status'].textContent; + assert.match(status, /2\/2 files/); + assert.match(status, /1 present/); + assert.match(status, /1 absent/); +}); diff --git a/frontend/tests/fs_render.test.js b/frontend/tests/fs_render.test.js index e938c23..464a9be 100644 --- a/frontend/tests/fs_render.test.js +++ b/frontend/tests/fs_render.test.js @@ -56,3 +56,12 @@ test('collapsed directory hides its children', () => { const out = pysimFsRenderNode(node, 0); assert.ok(!out.includes('EF.IMSI'), out); }); + +test('non-existing directory hides previously loaded children', () => { + const node = df('DF.WLAN', '5f40', { exists: false, expanded: true, children: [ef('EF.UPLMNWLAN', '4f42')] }); + const out = pysimFsRenderNode(node, 0); + assert.ok(out.includes('✗'), out); + assert.ok(!out.includes('EF.UPLMNWLAN'), out); + assert.ok(!out.includes('(empty)'), out); + assert.ok(!out.includes('pysimFsToggleDir'), out); +});