From a88dfc7b46629265d9342bd78557eb7e51311d6e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Jun 2026 23:23:14 +0000 Subject: [PATCH 1/5] Initial plan From e181e352fd62b44a3baba44d4d4d07b01495eb72 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Jun 2026 23:26:27 +0000 Subject: [PATCH 2/5] Show transitive package distinction in Project View --- src/features/views/treeViewItems.ts | 12 +++- .../features/views/treeViewItems.unit.test.ts | 66 ++++++++++++++++++- 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/src/features/views/treeViewItems.ts b/src/features/views/treeViewItems.ts index 0293afb7..2ed8b00a 100644 --- a/src/features/views/treeViewItems.ts +++ b/src/features/views/treeViewItems.ts @@ -433,10 +433,16 @@ export class ProjectPackage implements ProjectTreeItem { ) { this.id = ProjectPackage.getId(parent, pkg); const item = new TreeItem(this.pkg.displayName, TreeItemCollapsibleState.None); - item.iconPath = this.pkg.iconPath; + const defaultIcon = this.pkg.isTransitive ? new ThemeIcon('list-tree') : new ThemeIcon('package'); + item.iconPath = this.pkg.iconPath ?? defaultIcon; item.contextValue = this.pkg.isTransitive ? 'python-package-transitive' : 'python-package'; - item.description = this.pkg.description ?? this.pkg.version; - item.tooltip = this.pkg.tooltip; + item.description = + (this.pkg.isTransitive ? l10n.t('(transitive) ') : '') + (this.pkg.description ?? this.pkg.version); + item.tooltip = this.pkg.isTransitive + ? l10n.t( + 'This package is a dependency of another installed package. It may also have been explicitly installed.', + ) + : this.pkg.tooltip; this.treeItem = item; } diff --git a/src/test/features/views/treeViewItems.unit.test.ts b/src/test/features/views/treeViewItems.unit.test.ts index eca57557..10aa6353 100644 --- a/src/test/features/views/treeViewItems.unit.test.ts +++ b/src/test/features/views/treeViewItems.unit.test.ts @@ -1,14 +1,17 @@ import * as assert from 'assert'; -import { Uri } from 'vscode'; +import { ThemeIcon, Uri } from 'vscode'; +import { Package } from '../../../api'; import { UvInstallStrings, VenvManagerStrings } from '../../../common/localize'; import { EnvManagerTreeItem, getEnvironmentParentDirName, NoPythonEnvTreeItem, + ProjectEnvironment, + ProjectPackage, PythonEnvTreeItem, PythonGroupEnvTreeItem, } from '../../../features/views/treeViewItems'; -import { InternalEnvironmentManager, PythonEnvironmentImpl } from '../../../internal.api'; +import { InternalEnvironmentManager, InternalPackageManager, PythonEnvironmentImpl } from '../../../internal.api'; /** * Helper to create a mock PythonEnvironmentImpl with minimal required fields. @@ -487,4 +490,63 @@ suite('Test TreeView Items', () => { assert.equal(item.treeItem.command, undefined, 'Should not have a command'); }); }); + + suite('ProjectPackage', () => { + const parent = { id: 'project>>>env' } as ProjectEnvironment; + const manager = {} as InternalPackageManager; + + function createMockPackage(options: Partial = {}): Package { + return { + name: options.name ?? 'requests', + displayName: options.displayName ?? options.name ?? 'requests', + version: options.version, + description: options.description, + tooltip: options.tooltip, + iconPath: options.iconPath, + isTransitive: options.isTransitive, + pkgId: { id: options.name ?? 'requests', managerId: 'ms-python.python:pip' }, + } as Package; + } + + test('Direct package uses package icon and shows no transitive prefix', () => { + // Arrange + const pkg = createMockPackage({ name: 'requests', version: '2.31.0', isTransitive: false }); + + // Act + const item = new ProjectPackage(parent, pkg, manager); + + // Assert + assert.strictEqual(item.treeItem.contextValue, 'python-package'); + assert.strictEqual((item.treeItem.iconPath as ThemeIcon).id, 'package'); + assert.strictEqual(item.treeItem.description, '2.31.0'); + }); + + test('Transitive package uses list-tree icon and shows transitive prefix', () => { + // Arrange + const pkg = createMockPackage({ name: 'urllib3', version: '2.0.0', isTransitive: true }); + + // Act + const item = new ProjectPackage(parent, pkg, manager); + + // Assert + assert.strictEqual(item.treeItem.contextValue, 'python-package-transitive'); + assert.strictEqual((item.treeItem.iconPath as ThemeIcon).id, 'list-tree'); + assert.ok( + (item.treeItem.description as string).startsWith('(transitive) '), + 'Transitive package description should be prefixed with "(transitive) "', + ); + assert.ok(item.treeItem.tooltip, 'Transitive package should have an explanatory tooltip'); + }); + + test('Prefers package-provided iconPath over default icon', () => { + // Arrange + const pkg = createMockPackage({ name: 'numpy', isTransitive: true, iconPath: new ThemeIcon('symbol-numeric') }); + + // Act + const item = new ProjectPackage(parent, pkg, manager); + + // Assert + assert.strictEqual((item.treeItem.iconPath as ThemeIcon).id, 'symbol-numeric'); + }); + }); }); From 8325e3dfb8e63ad8d24c62858f6beaf22d7d84a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Jun 2026 23:27:43 +0000 Subject: [PATCH 3/5] Add empty description fallback and clarify test mocks --- src/features/views/treeViewItems.ts | 4 ++-- .../features/views/treeViewItems.unit.test.ts | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/features/views/treeViewItems.ts b/src/features/views/treeViewItems.ts index 2ed8b00a..04173e79 100644 --- a/src/features/views/treeViewItems.ts +++ b/src/features/views/treeViewItems.ts @@ -213,7 +213,7 @@ export class PackageTreeItem implements EnvTreeItem { const defaultIcon = pkg.isTransitive ? new ThemeIcon('list-tree') : new ThemeIcon('package'); item.iconPath = pkg.iconPath ?? defaultIcon; item.contextValue = pkg.isTransitive ? 'python-package-transitive' : 'python-package'; - item.description = (pkg.isTransitive ? l10n.t('(transitive) ') : '') + (pkg.description ?? pkg.version); + item.description = (pkg.isTransitive ? l10n.t('(transitive) ') : '') + (pkg.description ?? pkg.version ?? ''); item.tooltip = pkg.isTransitive ? l10n.t('This package is a dependency of another installed package. It may also have been explicitly installed.') : pkg.tooltip; @@ -437,7 +437,7 @@ export class ProjectPackage implements ProjectTreeItem { item.iconPath = this.pkg.iconPath ?? defaultIcon; item.contextValue = this.pkg.isTransitive ? 'python-package-transitive' : 'python-package'; item.description = - (this.pkg.isTransitive ? l10n.t('(transitive) ') : '') + (this.pkg.description ?? this.pkg.version); + (this.pkg.isTransitive ? l10n.t('(transitive) ') : '') + (this.pkg.description ?? this.pkg.version ?? ''); item.tooltip = this.pkg.isTransitive ? l10n.t( 'This package is a dependency of another installed package. It may also have been explicitly installed.', diff --git a/src/test/features/views/treeViewItems.unit.test.ts b/src/test/features/views/treeViewItems.unit.test.ts index 10aa6353..faf68958 100644 --- a/src/test/features/views/treeViewItems.unit.test.ts +++ b/src/test/features/views/treeViewItems.unit.test.ts @@ -492,6 +492,8 @@ suite('Test TreeView Items', () => { }); suite('ProjectPackage', () => { + // ProjectPackage only reads parent.id and does not call any manager methods, + // so minimal cast mocks are sufficient for exercising the tree item rendering. const parent = { id: 'project>>>env' } as ProjectEnvironment; const manager = {} as InternalPackageManager; @@ -538,8 +540,7 @@ suite('Test TreeView Items', () => { assert.ok(item.treeItem.tooltip, 'Transitive package should have an explanatory tooltip'); }); - test('Prefers package-provided iconPath over default icon', () => { - // Arrange + test('Prefers package-provided iconPath over default icon', () => { // Arrange const pkg = createMockPackage({ name: 'numpy', isTransitive: true, iconPath: new ThemeIcon('symbol-numeric') }); // Act @@ -548,5 +549,19 @@ suite('Test TreeView Items', () => { // Assert assert.strictEqual((item.treeItem.iconPath as ThemeIcon).id, 'symbol-numeric'); }); + + test('Falls back to empty description when version and description are missing', () => { + // Arrange + const directPkg = createMockPackage({ name: 'mypkg', isTransitive: false }); + const transitivePkg = createMockPackage({ name: 'mypkg', isTransitive: true }); + + // Act + const directItem = new ProjectPackage(parent, directPkg, manager); + const transitiveItem = new ProjectPackage(parent, transitivePkg, manager); + + // Assert + assert.strictEqual(directItem.treeItem.description, ''); + assert.strictEqual(transitiveItem.treeItem.description, '(transitive) '); + }); }); }); From 8f28a63d02ebb8af2ddf45301d549d28470689fa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Jun 2026 23:28:31 +0000 Subject: [PATCH 4/5] Fix test comment formatting --- src/test/features/views/treeViewItems.unit.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/features/views/treeViewItems.unit.test.ts b/src/test/features/views/treeViewItems.unit.test.ts index faf68958..7e7bc972 100644 --- a/src/test/features/views/treeViewItems.unit.test.ts +++ b/src/test/features/views/treeViewItems.unit.test.ts @@ -540,7 +540,8 @@ suite('Test TreeView Items', () => { assert.ok(item.treeItem.tooltip, 'Transitive package should have an explanatory tooltip'); }); - test('Prefers package-provided iconPath over default icon', () => { // Arrange + test('Prefers package-provided iconPath over default icon', () => { + // Arrange const pkg = createMockPackage({ name: 'numpy', isTransitive: true, iconPath: new ThemeIcon('symbol-numeric') }); // Act From 2df19422d4c4f99e10cde7c98a1311d12b1f5c35 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Jun 2026 23:33:15 +0000 Subject: [PATCH 5/5] Sort project view packages by type (direct before transitive) --- src/features/views/projectView.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/features/views/projectView.ts b/src/features/views/projectView.ts index e6fd7f3b..e8dda9d2 100644 --- a/src/features/views/projectView.ts +++ b/src/features/views/projectView.ts @@ -252,7 +252,9 @@ export class ProjectView implements TreeDataProvider { // Store the reference for refreshing packages this.packageRoots.set(uri ? uri.fsPath : 'global', environmentItem); - return packages.map((p) => new ProjectPackage(environmentItem, p, pkgManager)); + return packages + .sort((a, b) => (a.isTransitive === b.isTransitive ? 0 : a.isTransitive ? 1 : -1)) + .map((p) => new ProjectPackage(environmentItem, p, pkgManager)); } //return nothing if the element is not a project, environment, or undefined