component details matched tags showing correctly

This commit is contained in:
Smartsheet-JB-Brown 2025-04-15 13:09:04 -07:00
parent c0672358c7
commit 8eec97af25
5 changed files with 92 additions and 69 deletions

View file

@ -260,26 +260,30 @@ export class PackageManagerViewStateManager {
}
case "UPDATE_FILTERS": {
const { filters } = transition.payload as TransitionPayloads["UPDATE_FILTERS"]
const { filters = {} } = (transition.payload as TransitionPayloads["UPDATE_FILTERS"]) || {}
console.log("=== UPDATE_FILTERS Started ===", {
currentFilters: this.state.filters,
newFilters: filters,
})
// Create new filters object, preserving existing filters unless explicitly changed
const updatedFilters = {
type: filters.type ?? this.state.filters.type,
search: filters.search ?? this.state.filters.search,
tags: filters.tags ?? this.state.filters.tags,
}
// Update state with new filters
this.state = {
...this.state,
filters: {
...this.state.filters,
...filters,
},
filters: updatedFilters,
}
this.notifyStateChange()
// Send filter request immediately
vscode.postMessage({
type: "filterPackageManagerItems",
filters: this.state.filters,
filters: updatedFilters,
} as WebviewMessage)
console.log("=== UPDATE_FILTERS Finished ===")

View file

@ -445,6 +445,52 @@ describe("PackageManagerViewStateManager", () => {
},
})
})
it("should maintain filter criteria when search is cleared", async () => {
// Reset mock before test
;(vscode.postMessage as jest.Mock).mockClear()
// First set a type filter
await manager.transition({
type: "UPDATE_FILTERS",
payload: {
filters: { type: "mode" },
},
})
// Then add a search term
await manager.transition({
type: "UPDATE_FILTERS",
payload: {
filters: { search: "test" },
},
})
// Clear the search term
await manager.transition({
type: "UPDATE_FILTERS",
payload: {
filters: { search: "" },
},
})
// Should maintain type filter when search is cleared
expect(vscode.postMessage).toHaveBeenLastCalledWith({
type: "filterPackageManagerItems",
filters: {
type: "mode",
search: "",
tags: [],
},
})
const state = manager.getState()
expect(state.filters).toEqual({
type: "mode",
search: "",
tags: [],
})
})
})
describe("Message Handling", () => {

View file

@ -163,39 +163,14 @@ export const PackageManagerItemCard: React.FC<PackageManagerItemCardProps> = ({
{groupedItems && (
<ExpandableSection
title="Component Details"
badge={
filters.search
? (() => {
const matchCount =
item.items?.filter(
(subItem) =>
(subItem.metadata?.name || "")
.toLowerCase()
.includes(filters.search.toLowerCase()) ||
(subItem.metadata?.description || "")
.toLowerCase()
.includes(filters.search.toLowerCase()),
).length || 0
return matchCount > 0
? `${matchCount} match${matchCount !== 1 ? "es" : ""}`
: undefined
})()
: undefined
}
defaultExpanded={
!!filters.search &&
(item.items?.some(
(subItem) =>
(subItem.metadata?.name || "").toLowerCase().includes(filters.search.toLowerCase()) ||
(subItem.metadata?.description || "")
.toLowerCase()
.includes(filters.search.toLowerCase()),
) ||
false)
}>
badge={(() => {
const matchCount = item.items?.filter((subItem) => subItem.matchInfo?.matched).length ?? 0
return matchCount > 0 ? `${matchCount} match${matchCount !== 1 ? "es" : ""}` : undefined
})()}
defaultExpanded={item.items?.some((subItem) => subItem.matchInfo?.matched) ?? false}>
<div className="space-y-4">
{Object.entries(groupedItems).map(([type, group]) => (
<TypeGroup key={type} type={type} items={group.items} searchTerm={filters.search} />
<TypeGroup key={type} type={type} items={group.items} />
))}
</div>
</ExpandableSection>

View file

@ -8,12 +8,15 @@ interface TypeGroupProps {
description?: string
metadata?: any
path?: string
matchInfo?: {
matched: boolean
matchReason?: Record<string, boolean>
}
}>
className?: string
searchTerm?: string
}
export const TypeGroup: React.FC<TypeGroupProps> = ({ type, items, className, searchTerm }) => {
export const TypeGroup: React.FC<TypeGroupProps> = ({ type, items, className }) => {
const getTypeLabel = (type: string) => {
switch (type) {
case "mode":
@ -33,41 +36,31 @@ export const TypeGroup: React.FC<TypeGroupProps> = ({ type, items, className, se
return null
}
// Check if an item matches the search term
const itemMatchesSearch = (item: { name: string; description?: string }) => {
if (!searchTerm) return false
const term = searchTerm.toLowerCase()
return item.name.toLowerCase().includes(term) || (item.description || "").toLowerCase().includes(term)
}
return (
<div className={cn("mb-4", className)}>
<h4 className="text-sm font-medium text-vscode-foreground mb-2">{getTypeLabel(type)}</h4>
<ol className="list-decimal list-inside space-y-1">
{items.map((item, index) => {
const matches = itemMatchesSearch(item)
return (
<li
key={`${item.path || index}`}
className={cn(
"text-sm pl-1",
matches ? "text-vscode-foreground font-medium" : "text-vscode-foreground",
)}
title={item.path}>
<span className={cn("font-medium", matches ? "text-vscode-textLink" : "")}>
{item.name}
{items.map((item, index) => (
<li
key={`${item.path || index}`}
className={cn(
"text-sm pl-1",
item.matchInfo?.matched ? "text-vscode-foreground font-medium" : "text-vscode-foreground",
)}
title={item.path}>
<span className={cn("font-medium", item.matchInfo?.matched ? "text-vscode-textLink" : "")}>
{item.name}
</span>
{item.description && (
<span className="text-vscode-descriptionForeground"> - {item.description}</span>
)}
{item.matchInfo?.matched && (
<span className="ml-2 text-xs bg-vscode-badge-background text-vscode-badge-foreground px-1 py-0.5 rounded">
match
</span>
{item.description && (
<span className="text-vscode-descriptionForeground"> - {item.description}</span>
)}
{matches && (
<span className="ml-2 text-xs bg-vscode-badge-background text-vscode-badge-foreground px-1 py-0.5 rounded">
match
</span>
)}
</li>
)
})}
)}
</li>
))}
</ol>
</div>
)

View file

@ -8,6 +8,10 @@ export interface GroupedItems {
description?: string
metadata?: any
path?: string
matchInfo?: {
matched: boolean
matchReason?: Record<string, boolean>
}
}>
}
}
@ -39,6 +43,7 @@ export function groupItemsByType(items: PackageManagerItem["items"] = []): Group
description: item.metadata?.description,
metadata: item.metadata,
path: item.path,
matchInfo: item.matchInfo,
})
return groups