fix(FN-1987): move skills search to top and improve catalog fallback

- Move the shared skills search input to the top of SkillsView so it applies to discovered skills and catalog results
- Add client-side discovered skills filtering by name/path with a dedicated filtered-empty-state message
- Extend skills adapter fallback logic to use unauthenticated catalog search when authenticated requests return 400, 401, or 403
- Add dashboard component and adapter tests covering discovered filtering behavior and authenticated-endpoint fallback scenarios
This commit is contained in:
Fusion
2026-04-17 17:06:09 -07:00
committed by gsxdsm
parent c6f3b8c074
commit 81d2cbeb56
5 changed files with 314 additions and 17 deletions

View File

@@ -620,4 +620,74 @@ describe("SkillsView", () => {
});
});
});
describe("discovered skills filtering", () => {
it("filters discovered skills by search query", async () => {
render(<SkillsView addToast={mockAddToast} onClose={onClose} />);
await waitFor(() => {
expect(screen.getByText("test-skill")).toBeTruthy();
expect(screen.getByText("another-skill")).toBeTruthy();
});
const searchInput = screen.getByPlaceholderText("Search skills...");
fireEvent.change(searchInput, { target: { value: "test-skill" } });
await waitFor(() => {
expect(screen.getByText("test-skill")).toBeTruthy();
expect(screen.queryByText("another-skill")).toBeNull();
});
});
it("shows filtered empty state when no discovered skills match search", async () => {
render(<SkillsView addToast={mockAddToast} onClose={onClose} />);
await waitFor(() => {
expect(screen.getByText("test-skill")).toBeTruthy();
});
const searchInput = screen.getByPlaceholderText("Search skills...");
fireEvent.change(searchInput, { target: { value: "zzz-nonexistent" } });
await waitFor(() => {
expect(screen.getByText("No discovered skills match your search.")).toBeTruthy();
});
});
it("shows original empty state when no skills are discovered", async () => {
mockFetchDiscoveredSkills.mockResolvedValue([]);
render(<SkillsView addToast={mockAddToast} onClose={onClose} />);
await waitFor(() => {
expect(screen.getByText("No skills discovered in this project.")).toBeTruthy();
});
// Search should not override the "no skills discovered" empty state
const searchInput = screen.getByPlaceholderText("Search skills...");
fireEvent.change(searchInput, { target: { value: "test" } });
await waitFor(() => {
expect(screen.getByText("No skills discovered in this project.")).toBeTruthy();
});
});
it("filters discovered skills by relativePath", async () => {
render(<SkillsView addToast={mockAddToast} onClose={onClose} />);
await waitFor(() => {
expect(screen.getByText("test-skill")).toBeTruthy();
expect(screen.getByText("another-skill")).toBeTruthy();
});
// Search by path instead of name
const searchInput = screen.getByPlaceholderText("Search skills...");
fireEvent.change(searchInput, { target: { value: "another-skill" } });
await waitFor(() => {
expect(screen.queryByText("test-skill")).toBeNull();
expect(screen.getByText("another-skill")).toBeTruthy();
});
});
});
});