Skip to content

Commit f6c93fb

Browse files
committed
fix a search crash
1 parent 3cf4e01 commit f6c93fb

File tree

5 files changed

+145
-7
lines changed

5 files changed

+145
-7
lines changed

packages/react/src/components/header/searchbar/components/AutocompleteDropdownItem.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ export function AutocompleteDropdownItem({
2727
className={"cursor-pointer"}
2828
{...rest}
2929
>
30-
<span className="font-medium text-base-11">
30+
<span className="mr-1 font-medium text-base-11">
3131
{t(`search.class.${item.type}`, item.type)}
32-
{": "}
32+
{":"}
3333
</span>
3434
{item.incomplete ? (
3535
<span className="font-normal text-base-9">

packages/react/src/components/header/searchbar/components/SearchBar.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export function SearchBar({
108108
shouldFilter={false}
109109
>
110110
<Popover
111-
open={open && autocomplete.length > 0}
111+
open={open}
112112
onOpenChange={() => {
113113
/* ignore the event, basically popover needs to be mostly inert and controlled by the focus state of the input */
114114
}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { describe, it, expect } from "vitest";
2+
import { splitSearchClassTerms } from "./helper";
3+
import { JSON_SCHEMA } from "./types";
4+
5+
describe("splitSearchClassTerms", () => {
6+
// Setup mock language category mapping
7+
const mockLangCategoryMap = {
8+
org: "org",
9+
組織: "org",
10+
from: "from",
11+
から: "from",
12+
to: "to",
13+
まで: "to",
14+
search: "search",
15+
検索: "search",
16+
description: "description",
17+
説明: "description",
18+
type: "type",
19+
タイプ: "type",
20+
topic: "topic",
21+
トピック: "topic",
22+
vtuber: "vtuber",
23+
lang: "lang",
24+
言語: "lang",
25+
has_song: "has_song",
26+
歌あり: "has_song",
27+
} as Record<string, keyof typeof JSON_SCHEMA>;
28+
29+
it("should correctly split valid category and search term", () => {
30+
const result = splitSearchClassTerms("org:hololive", mockLangCategoryMap);
31+
expect(result).toEqual(["org", "hololive"]);
32+
});
33+
34+
it("should handle Japanese colons and categories", () => {
35+
const result = splitSearchClassTerms(
36+
"組織:ホロライブ",
37+
mockLangCategoryMap,
38+
);
39+
expect(result).toEqual(["org", "ホロライブ"]);
40+
});
41+
42+
it("should correctly handle date categories", () => {
43+
const result = splitSearchClassTerms(
44+
"from:2024-01-01",
45+
mockLangCategoryMap,
46+
);
47+
expect(result).toEqual(["from", "2024-01-01"]);
48+
});
49+
50+
it("should handle Japanese date categories", () => {
51+
const result = splitSearchClassTerms(
52+
"から:2024-01-01",
53+
mockLangCategoryMap,
54+
);
55+
expect(result).toEqual(["from", "2024-01-01"]);
56+
});
57+
58+
it("should return undefined category for invalid category prefix", () => {
59+
const result = splitSearchClassTerms("invalid:term", mockLangCategoryMap);
60+
expect(result).toEqual([undefined, "invalid:term"]);
61+
});
62+
63+
it("should handle search terms without category prefix", () => {
64+
const result = splitSearchClassTerms("searchterm", mockLangCategoryMap);
65+
expect(result).toEqual([undefined, "searchterm"]);
66+
});
67+
68+
it("should handle empty search value after category", () => {
69+
const result = splitSearchClassTerms("org:", mockLangCategoryMap);
70+
expect(result).toEqual(["org", ""]);
71+
const result2 = splitSearchClassTerms("org", mockLangCategoryMap);
72+
expect(result2).toEqual(["org", ""]);
73+
});
74+
75+
it("should handle whitespace around category and value", () => {
76+
const result = splitSearchClassTerms(
77+
" type : stream ",
78+
mockLangCategoryMap,
79+
);
80+
expect(result).toEqual(["type", "stream"]);
81+
});
82+
83+
it("should handle array type categories", () => {
84+
const result = splitSearchClassTerms("topic:music", mockLangCategoryMap);
85+
expect(result).toEqual(["topic", "music"]);
86+
});
87+
88+
it("should handle string type categories", () => {
89+
const result = splitSearchClassTerms(
90+
"description:test content",
91+
mockLangCategoryMap,
92+
);
93+
expect(result).toEqual(["description", "test content"]);
94+
});
95+
96+
it("should preserve colons in search term when no valid category", () => {
97+
const result = splitSearchClassTerms(
98+
"something:else:here",
99+
mockLangCategoryMap,
100+
);
101+
expect(result).toEqual([undefined, "something:else:here"]);
102+
});
103+
104+
it("should handle empty string input", () => {
105+
const result = splitSearchClassTerms("", mockLangCategoryMap);
106+
expect(result).toEqual([undefined, ""]);
107+
});
108+
});

packages/react/src/components/header/searchbar/hooks/useClientSuggestions.test.ts

+29
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,35 @@ describe("useClientSuggestions", () => {
7878
]);
7979
});
8080

81+
it("handles ONLY a SearchCategory by providing a incomplete QueryItem", () => {
82+
const { result } = renderHook(() =>
83+
useClientSuggestions("topic", "", mockT),
84+
);
85+
86+
expect(result.current).toEqual([
87+
{
88+
type: "topic",
89+
value: "",
90+
text: "",
91+
incomplete: true,
92+
},
93+
]);
94+
});
95+
96+
it("handles PARTIAL searchCategory by providing a matched QueryItem", () => {
97+
const { result } = renderHook(() =>
98+
useClientSuggestions(undefined, "Topi", mockT),
99+
);
100+
101+
expect(result.current).toEqual([
102+
{
103+
type: "search",
104+
value: "Topi",
105+
text: "Topi",
106+
},
107+
]);
108+
});
109+
81110
it("limits organization suggestions to 5 when no category specified", () => {
82111
const manyOrgs = Array.from({ length: 10 }, (_, i) => ({
83112
name: `Munchies ${i}`,

packages/react/src/components/header/searchbar/hooks/useClientSuggestions.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,12 @@ export function useClientSuggestions(
6464
// Handle category-specific static suggestions that only show up when a category is specified
6565
else if (searchCategory && STATIC_SUGGESTIONS[searchCategory]) {
6666
suggestions.push(...STATIC_SUGGESTIONS[searchCategory]);
67-
} else if (searchCategory) {
67+
} else if (searchCategory && !searchString) {
6868
suggestions.push({
6969
type: searchCategory,
7070
value: searchString,
7171
text: searchString,
72+
incomplete: true,
7273
});
7374
}
7475

@@ -85,9 +86,9 @@ export function useClientSuggestions(
8586
const categoryAutofill = FIRST_SEARCH.filter(
8687
(x) =>
8788
!searchString ||
88-
t(`search.class.${x.type}`, { defaultValue: x.type }).startsWith(
89-
searchString,
90-
),
89+
t(`search.class.${x.type}`, { defaultValue: x.type })
90+
.toLowerCase()
91+
.startsWith(searchString.toLowerCase()),
9192
);
9293
suggestions.push(...categoryAutofill);
9394
}

0 commit comments

Comments
 (0)