diff --git a/classifier/js/tree.js b/classifier/js/tree.js index ea6bd17..b24b509 100644 --- a/classifier/js/tree.js +++ b/classifier/js/tree.js @@ -111,33 +111,39 @@ var visible = null; // { folders, files } while filtering, else null function computeVisible() { var c = window.app.modules.classify; - var folders = Object.create(null), files = Object.create(null); + var folders = Object.create(null), files = Object.create(null), counts = Object.create(null); var nf = filterActive(); function walk(folder, ancMatched) { var selfMatch = nf && nameHit(folder.path || folder.name); var matched = ancMatched || selfMatch; var show = false, hasFile = false, descMatch = false; + // Post-filter counts for the row's "direct+total" badge: direct = + // immediate visible children/files, total = visible across the subtree. + var dDir = 0, tDir = 0, dFile = 0, tFile = 0; (folder.children || []).forEach(function (ch) { var r = walk(ch, matched); - if (r.show) show = true; + if (r.show) { show = true; dDir++; tDir += 1 + r.tDir; } if (r.hasFile) hasFile = true; if (r.subtreeMatch) descMatch = true; // a child leads to a match + tFile += r.tFile; }); (folder.files || []).forEach(function (f) { hasFile = true; if (!classifyAllows(f)) return; var fileMatch = nf && nameHit(c.srcKeyForFile(f)); - if (!nf || matched || fileMatch) { files[c.srcKeyForFile(f)] = true; show = true; } + if (!nf || matched || fileMatch) { files[c.srcKeyForFile(f)] = true; show = true; dFile++; } if (fileMatch) descMatch = true; // a match sits directly in this folder }); + tFile += dFile; if (matched) show = true; // "Show Empty" off → hide folders whose whole subtree holds no files. if (!hasFile && !showEmpty && !matched) show = false; if (show) folders[folder.path] = true; - return { show: show, hasFile: hasFile, subtreeMatch: descMatch || selfMatch }; + counts[folder.path] = { dDir: dDir, tDir: tDir, dFile: dFile, tFile: tFile }; + return { show: show, hasFile: hasFile, subtreeMatch: descMatch || selfMatch, tDir: tDir, tFile: tFile }; } (window.app.folderTree || []).forEach(function (root) { walk(root, false); }); - return { folders: folders, files: files }; + return { folders: folders, files: files, counts: counts }; } function folderShown(folder) { return !visible || !!visible.folders[folder.path]; } function fileShown(file) { @@ -211,8 +217,13 @@ const done = st === 'done'; // When fully scanned both numbers are blue; .done turns the labels blue too. if (done) el.classList.add('done'); - const dDir = folder.subdirCount || 0, tDir = folder.runDirs || 0; - const dFile = folder.fileCount || 0, tFile = folder.runFiles || 0; + // While a filter (autofilter or a Show checkbox) is narrowing the tree, + // the badge counts what's VISIBLE; otherwise the raw scanned totals. + const vc = visible && visible.counts && visible.counts[folder.path]; + const dDir = vc ? vc.dDir : (folder.subdirCount || 0); + const tDir = vc ? vc.tDir : (folder.runDirs || 0); + const dFile = vc ? vc.dFile : (folder.fileCount || 0); + const tFile = vc ? vc.tFile : (folder.runFiles || 0); const frag = document.createDocumentFragment(); frag.appendChild(document.createTextNode('(')); diff --git a/tests/classify.spec.js b/tests/classify.spec.js index 9bad2cd..073703e 100644 --- a/tests/classify.spec.js +++ b/tests/classify.spec.js @@ -849,6 +849,35 @@ test('filter does not open collapsed branches; non-matching siblings hide', asyn expect(r.files).toEqual([]); }); +test('folder count badge shows post-filter totals', async ({ page }) => { + await page.click('#modeClassifyBtn'); + const r = await page.evaluate(() => { + window.app.folderTree = [{ + name: 'Root', path: 'Root', expanded: true, scanState: 'done', + subdirCount: 2, runDirs: 2, fileCount: 0, runFiles: 3, files: [], children: [ + { name: 'A', path: 'Root/A', expanded: true, scanState: 'done', subdirCount: 0, runDirs: 0, fileCount: 2, runFiles: 2, children: [], files: [ + { originalFilename: 'alpha report', extension: 'pdf', folderPath: 'Root/A' }, + { originalFilename: 'beta memo', extension: 'pdf', folderPath: 'Root/A' }, + ] }, + { name: 'B', path: 'Root/B', expanded: true, scanState: 'done', subdirCount: 0, runDirs: 0, fileCount: 1, runFiles: 1, children: [], files: [ + { originalFilename: 'gamma note', extension: 'pdf', folderPath: 'Root/B' }, + ] }, + ], + }]; + const tree = window.app.modules.tree; + const rootCount = () => { const e = document.querySelector('#folderTree .folder-item .folder-count'); return e ? e.textContent : null; }; + tree.render(); + const before = rootCount(); // no filter → raw scan totals + tree.setNameFilter('alpha'); // matches one file, in folder A only + const after = rootCount(); + return { before, after }; + }); + expect(r.before).toContain('2 folders'); // raw: 2 subfolders… + expect(r.before).toContain('0+3 files'); // …3 files in the subtree + expect(r.after).toContain('1 folder'); // filtered: only A is visible + expect(r.after).toContain('0+1 file'); // …holding the single matching file +}); + test('snapshot: a scanned zip subtree round-trips with its virtual members', async ({ page }) => { const r = await page.evaluate(() => { const sc = window.app.modules.scanner;