From abc8730531bd5cb0094d88cec629af0517031eab Mon Sep 17 00:00:00 2001 From: Stefan Hayden Date: Tue, 19 May 2026 16:14:08 -0400 Subject: [PATCH] fix(trees): let remap['file-tree-icon-file'] override the built-in 'default' fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With set:'standard' or set:'complete', resolveBuiltInFileIconToken always returns a token — including 'default' for files with no specific match. This caused the iconRemap check to be skipped entirely, making remap['file-tree-icon-file'] unreachable as a generic file icon fallback unless the caller used set:'minimal'. Fix: when the resolved built-in token is the generic 'default', check iconRemap['file-tree-icon-file'] first. Specific tokens (typescript, python, etc.) are unaffected. Falls back to file-tree-builtin-default if no remap entry is defined, preserving existing behaviour. --- packages/trees/src/render/iconResolver.ts | 9 ++++ .../trees/test/file-tree-icon-config.test.ts | 47 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/packages/trees/src/render/iconResolver.ts b/packages/trees/src/render/iconResolver.ts index 254dd5223..3b2739096 100644 --- a/packages/trees/src/render/iconResolver.ts +++ b/packages/trees/src/render/iconResolver.ts @@ -109,6 +109,15 @@ export function createFileTreeIconResolver(icons?: FileTreeIcons): { extensionCandidates ); if (builtInToken != null && normalizedIcons.set !== 'none') { + // When the resolved token is the generic 'default' fallback, let the + // user's remap['file-tree-icon-file'] win — that slot is explicitly + // meant to override the generic file placeholder. + if (builtInToken === 'default') { + const remappedEntry = iconRemap?.[name]; + if (remappedEntry != null) { + return remapEntryToIcon(remappedEntry, name); + } + } return { name: getBuiltInFileIconName(builtInToken), remappedFrom: name, diff --git a/packages/trees/test/file-tree-icon-config.test.ts b/packages/trees/test/file-tree-icon-config.test.ts index 53d304422..ecb47a9d8 100644 --- a/packages/trees/test/file-tree-icon-config.test.ts +++ b/packages/trees/test/file-tree-icon-config.test.ts @@ -102,6 +102,53 @@ describe('file-tree icon config', () => { } }); + test('remap[file-tree-icon-file] overrides the default built-in fallback when set is complete', async () => { + const { cleanup, dom } = installDom(); + try { + const { FileTree } = await import('../src/render/FileTree'); + const mount = dom.window.document.createElement('div'); + dom.window.document.body.appendChild(mount); + + const fileTree = new FileTree({ + flattenEmptyDirectories: true, + icons: { + set: 'complete', + spriteSheet: + '', + remap: { + 'file-tree-icon-file': 'pst-test-generic-file', + }, + }, + initialExpansion: 'open', + paths: ['unknown.xyz', 'src/index.ts'], + initialVisibleRowCount: 120 / 30, + }); + + fileTree.render({ containerWrapper: mount }); + await flushDom(); + + const shadowRoot = fileTree.getFileTreeContainer()?.shadowRoot; + + // Unknown extension has no specific built-in token — remap should win + // over the generic 'default' fallback. + const unknownButton = getItemButton(shadowRoot, dom, 'unknown.xyz'); + expect(unknownButton.querySelector('use')?.getAttribute('href')).toBe( + '#pst-test-generic-file' + ); + + // Known extension has a specific built-in token — remap must not + // override it. + const tsButton = getItemButton(shadowRoot, dom, 'src/index.ts'); + expect(tsButton.querySelector('use')?.getAttribute('href')).toBe( + '#file-tree-builtin-typescript' + ); + + fileTree.cleanUp(); + } finally { + cleanup(); + } + }); + test('setIcons swaps icon modes without resetting expanded state', async () => { const { cleanup, dom } = installDom(); try {