fix(file-icons): 适配 v0.18.1 基线——重命名行接入解析器 + 测试与门禁补齐
rebase 到 main(v0.18.1)后的适配层: - 冲突(EditorHost ×2 / TreePanel / FileTree)按并集解:main 侧 onPathRenamed/onPathDeleted 与 PR 侧 service prop 并存。 - main 新增的行内重命名行仍用 <VscFile>/<VscFolder>,而本次改动已删除 VscFile import(合并后 TS2304)——改走 dirRowIcon/fileRowIcon,与树行 同一条回退链;VscFolder 随之无引用,从 import 移除。 - tests/file-icons.spec.tsx:markerB 换成独立元素类型(<b>),使 「priority 降序 / 同级按注册序」断言真正可区分(此前两个 marker 都是 <span>,断言恒真);新增「工厂返回 undefined 则拒绝该级、链继续」与 「注册/注销触发订阅通知」两例(17 → 19)。 - tests/consumer-types.ts:补齐 FileIconDescriptor / registerFileIcon / getFileIcons / matchFileIcon / matchFolderIcon / fileIcon / folderIcon / FOLDER_EXT / FOLDER_OPEN_EXT 的编译期门禁。 - README:v0.19.0(未发布)段移到「最近更新」最前(此前被自动合并夹在 v0.18.1 与 v0.18.0 之间)。 - file-icons.tsx:注释修正——展平后的 Map 是后写覆盖,不是「first match wins」;当前无重复扩展名键,仅潜在语义。
This commit is contained in:
@@ -12,12 +12,15 @@
|
||||
*/
|
||||
import type {} from '../src/client/service.ts'
|
||||
import {
|
||||
FOLDER_EXT,
|
||||
FOLDER_OPEN_EXT,
|
||||
SIDEBAR_FEATURES,
|
||||
SIDEBAR_SERVICE_VERSION,
|
||||
} from '../src/client/service.ts'
|
||||
import type {
|
||||
BetterSidebarService,
|
||||
FileFetchStrategy,
|
||||
FileIconDescriptor,
|
||||
FileViewerDescriptor,
|
||||
FileViewerProps,
|
||||
OpenTabSeed,
|
||||
@@ -125,6 +128,23 @@ service.updateTab('tab:1', { title: 'T', path: '/p', meta: 1 })
|
||||
service.activateTab('tab:1')
|
||||
service.openFile({ sessionId: 's1', cwd: '/p' }, '/p/a.csv', 'Data')
|
||||
|
||||
/** File-icon registration surface (feature `fileIcons`). */
|
||||
const icon: FileIconDescriptor = {
|
||||
id: 'my-plugin:icons',
|
||||
exts: ['csv', FOLDER_EXT, FOLDER_OPEN_EXT],
|
||||
priority: 5,
|
||||
icon: (path: string, size: number) => {
|
||||
void path; void size
|
||||
return null
|
||||
},
|
||||
}
|
||||
service.registerFileIcon(icon)
|
||||
service.getFileIcons()
|
||||
service.matchFileIcon('/p/a.csv')
|
||||
service.matchFolderIcon(true)
|
||||
service.fileIcon('/p/a.csv', 14)
|
||||
service.folderIcon('/p', true, 14)
|
||||
|
||||
/** Named state vocabulary stays importable (the pre-0.12 gap). */
|
||||
const diff: SidebarDiffRef = { kind: 'worktree', path: '/p/a.ts', staged: false }
|
||||
const prefs: SidebarPrefs = { ...({} as SidebarPrefs) }
|
||||
|
||||
@@ -35,7 +35,9 @@ const glyphOf = (node: ReactNode): unknown => (node as ReactElement).type
|
||||
/** A marker component standing in for a plugin's registered icon. */
|
||||
const marker = (): ReactNode => <span data-marker />
|
||||
|
||||
const markerB = (): ReactNode => <span data-marker-b />
|
||||
/** A second marker with a DISTINCT element type, so `glyphOf` can tell the
|
||||
* two registrations apart in priority/ordering assertions. */
|
||||
const markerB = (): ReactNode => <b data-marker-b />
|
||||
|
||||
describe('file icon registration API', () => {
|
||||
it('registerFileIcon adds to the registry and dispose removes it', () => {
|
||||
@@ -80,6 +82,22 @@ describe('file icon registration API', () => {
|
||||
it('the feature is advertised in SIDEBAR_FEATURES', () => {
|
||||
expect(SIDEBAR_FEATURES.includes('fileIcons')).toBe(true)
|
||||
})
|
||||
|
||||
it('register and dispose notify subscribers (mounted rows re-resolve, no reload)', () => {
|
||||
const service = createBetterSidebarService(createSidebarStore())
|
||||
let notified = 0
|
||||
const unsubscribe = service.subscribe(() => { notified += 1 })
|
||||
const dispose = service.registerFileIcon({ id: 'test:icons', exts: ['csv'], icon: marker })
|
||||
expect(notified).toBe(1)
|
||||
dispose()
|
||||
expect(notified).toBe(2)
|
||||
// A second dispose is a no-op — no spurious notification.
|
||||
dispose()
|
||||
expect(notified).toBe(2)
|
||||
unsubscribe()
|
||||
service.registerFileIcon({ id: 'test:other', exts: ['tsv'], icon: marker })
|
||||
expect(notified).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('fileIcon resolver chain (specific → builtin → catch-all → VscFile)', () => {
|
||||
@@ -102,6 +120,23 @@ describe('fileIcon resolver chain (specific → builtin → catch-all → VscFil
|
||||
const service = createBetterSidebarService(createSidebarStore())
|
||||
service.registerFileIcon({ id: 'all-a', exts: [], icon: marker })
|
||||
service.registerFileIcon({ id: 'all-b', exts: [], priority: 5, icon: markerB })
|
||||
// markerB ('b') is the higher-priority catch-all — not merely "some span".
|
||||
expect(glyphOf(service.fileIcon('/w/Makefile', 14))).toBe('b')
|
||||
const tie = createBetterSidebarService(createSidebarStore())
|
||||
tie.registerFileIcon({ id: 'first', exts: [], icon: marker })
|
||||
tie.registerFileIcon({ id: 'second', exts: [], icon: markerB })
|
||||
// Equal priority keeps registration order (the first-registered wins).
|
||||
expect(glyphOf(tie.fileIcon('/w/Makefile', 14))).toBe('span')
|
||||
})
|
||||
|
||||
it('a factory returning undefined declines that link and the chain continues', () => {
|
||||
const service = createBetterSidebarService(createSidebarStore())
|
||||
service.registerFileIcon({ id: 'md-decline', exts: ['md'], icon: () => undefined })
|
||||
// The specific registration declined → the built-in glyph claims it.
|
||||
expect(glyphOf(service.fileIcon('/w/README.md', 14))).toBe(VscMarkdown)
|
||||
service.registerFileIcon({ id: 'all-decline', exts: [], priority: 10, icon: () => undefined })
|
||||
service.registerFileIcon({ id: 'all-take', exts: [], icon: marker })
|
||||
// The declining catch-all is skipped, the next one takes the row.
|
||||
expect(glyphOf(service.fileIcon('/w/Makefile', 14))).toBe('span')
|
||||
})
|
||||
|
||||
@@ -153,9 +188,10 @@ describe('folderIcon resolver (registered folder/folder-open → builtin glyphs)
|
||||
// The OPEN row is a separate reserved ext — still the builtin here.
|
||||
expect(glyphOf(service.folderIcon('/w/src', true, 14))).toBe(VscFolderOpened)
|
||||
service.registerFileIcon({ id: 'open', exts: ['folder-open'], icon: markerB })
|
||||
expect(glyphOf(service.folderIcon('/w/src', true, 14))).toBe('span')
|
||||
expect(glyphOf(service.folderIcon('/w/src', true, 14))).toBe('b')
|
||||
service.registerFileIcon({ id: 'closed-hi', exts: ['folder'], priority: 3, icon: markerB })
|
||||
expect(glyphOf(service.folderIcon('/w/src', false, 14))).toBe('span')
|
||||
// The higher-priority registration ('b') takes the closed row.
|
||||
expect(glyphOf(service.folderIcon('/w/src', false, 14))).toBe('b')
|
||||
})
|
||||
|
||||
it('a catch-all registration never claims a directory', () => {
|
||||
|
||||
Reference in New Issue
Block a user