diff --git a/webview-ui/src/components/package-manager/PackageManagerViewStateManager.ts b/webview-ui/src/components/package-manager/PackageManagerViewStateManager.ts index 54d2b9019f..42414ea3cc 100644 --- a/webview-ui/src/components/package-manager/PackageManagerViewStateManager.ts +++ b/webview-ui/src/components/package-manager/PackageManagerViewStateManager.ts @@ -183,12 +183,13 @@ export class PackageManagerViewStateManager { // Clear any existing timeout this.clearFetchTimeout() - // Create a new state object + // Create a new state object with sorted items + const sortedItems = this.sortItems([...items]) const newState = { ...this.state, isFetching: false, - allItems: [...items], - displayItems: this.isFilterActive() ? this.state.displayItems : [...items], + allItems: sortedItems, + displayItems: this.isFilterActive() ? this.state.displayItems : sortedItems, } // If filters are active, apply them to the new items @@ -337,6 +338,11 @@ export class PackageManagerViewStateManager { ...this.state.sortConfig, ...sortConfig, } + // Apply sorting to both allItems and displayItems + this.state.allItems = this.sortItems(this.state.allItems) + if (this.state.displayItems) { + this.state.displayItems = this.sortItems(this.state.displayItems) + } this.notifyStateChange() break } @@ -412,6 +418,23 @@ export class PackageManagerViewStateManager { return !!(this.state.filters.type || this.state.filters.search || this.state.filters.tags.length > 0) } + private sortItems(items: PackageManagerItem[]): PackageManagerItem[] { + const { by, order } = this.state.sortConfig + return [...items].sort((a, b) => { + let aValue = a[by] || "" + let bValue = b[by] || "" + + // Handle dates for lastUpdated + if (by === "lastUpdated") { + aValue = aValue || "1970-01-01T00:00:00Z" + bValue = bValue || "1970-01-01T00:00:00Z" + } + + const comparison = aValue.localeCompare(bValue) + return order === "asc" ? comparison : -comparison + }) + } + public async handleMessage(message: any): Promise { console.log("=== Handling Message ===", { messageType: message.type, diff --git a/webview-ui/src/components/package-manager/__tests__/PackageManagerViewStateManager.test.ts b/webview-ui/src/components/package-manager/__tests__/PackageManagerViewStateManager.test.ts index 263103826d..201f3160f4 100644 --- a/webview-ui/src/components/package-manager/__tests__/PackageManagerViewStateManager.test.ts +++ b/webview-ui/src/components/package-manager/__tests__/PackageManagerViewStateManager.test.ts @@ -843,19 +843,103 @@ describe("PackageManagerViewStateManager", () => { }) describe("Sort Transitions", () => { - it("should handle UPDATE_SORT transition", async () => { - const sortConfig = { - by: "lastUpdated" as const, - order: "desc" as const, - } + it("should sort items by name in ascending order", async () => { + const items = [ + createTestItem({ name: "B Component" }), + createTestItem({ name: "A Component" }), + createTestItem({ name: "C Component" }), + ] + + await manager.transition({ + type: "FETCH_COMPLETE", + payload: { items }, + }) await manager.transition({ type: "UPDATE_SORT", - payload: { sortConfig }, + payload: { sortConfig: { by: "name", order: "asc" } }, }) const state = manager.getState() - expect(state.sortConfig).toEqual(sortConfig) + expect(state.allItems[0].name).toBe("A Component") + expect(state.allItems[1].name).toBe("B Component") + expect(state.allItems[2].name).toBe("C Component") + }) + + it("should sort items by lastUpdated in descending order", async () => { + const items = [ + createTestItem({ lastUpdated: "2025-04-13T09:00:00-07:00" }), + createTestItem({ lastUpdated: "2025-04-14T09:00:00-07:00" }), + createTestItem({ lastUpdated: "2025-04-12T09:00:00-07:00" }), + ] + + await manager.transition({ + type: "FETCH_COMPLETE", + payload: { items }, + }) + + await manager.transition({ + type: "UPDATE_SORT", + payload: { sortConfig: { by: "lastUpdated", order: "desc" } }, + }) + + const state = manager.getState() + expect(state.allItems[0].lastUpdated).toBe("2025-04-14T09:00:00-07:00") + expect(state.allItems[1].lastUpdated).toBe("2025-04-13T09:00:00-07:00") + expect(state.allItems[2].lastUpdated).toBe("2025-04-12T09:00:00-07:00") + }) + + it("should maintain sort order when items are updated", async () => { + const items = [ + createTestItem({ name: "B Component" }), + createTestItem({ name: "A Component" }), + createTestItem({ name: "C Component" }), + ] + + await manager.transition({ + type: "FETCH_COMPLETE", + payload: { items }, + }) + + await manager.transition({ + type: "UPDATE_SORT", + payload: { sortConfig: { by: "name", order: "asc" } }, + }) + + // Add a new item + const newItems = [...items, createTestItem({ name: "D Component" })] + + await manager.transition({ + type: "FETCH_COMPLETE", + payload: { items: newItems }, + }) + + const state = manager.getState() + expect(state.allItems[0].name).toBe("A Component") + expect(state.allItems[1].name).toBe("B Component") + expect(state.allItems[2].name).toBe("C Component") + expect(state.allItems[3].name).toBe("D Component") + }) + + it("should handle missing values gracefully", async () => { + const items = [ + createTestItem({ name: "B Component", lastUpdated: undefined }), + createTestItem({ name: "A Component", lastUpdated: "2025-04-14T09:00:00-07:00" }), + ] + + await manager.transition({ + type: "FETCH_COMPLETE", + payload: { items }, + }) + + await manager.transition({ + type: "UPDATE_SORT", + payload: { sortConfig: { by: "lastUpdated", order: "desc" } }, + }) + + const state = manager.getState() + expect(state.allItems[0].lastUpdated).toBe("2025-04-14T09:00:00-07:00") + expect(state.allItems[1].lastUpdated).toBeUndefined() }) })