Skip to content

Commit 8feecd0

Browse files
committed
fix(settings): improve search functionality and UI integration
Fix search logic issues and enhance UI styling: - Fix minimum query length validation (require 2+ characters) - Add comprehensive debug logging for search operations - Optimize search component styles for Obsidian integration - Use Obsidian's native CSS variables for consistent styling - Force override default input/button styles that conflict - Improve keyboard navigation and accessibility - Add responsive design and proper scrollbar styling - Enhance search result display with proper spacing - Add comprehensive test suite for search functionality Technical improvements: - Replace hardcoded values with Obsidian CSS variables - Fix box-shadow and border conflicts with \!important flags - Standardize icon sizes and button dimensions - Improve focus states and interaction feedback - Add proper z-index layering for popover elements
1 parent 8a8dec0 commit 8feecd0

5 files changed

Lines changed: 454 additions & 59 deletions

File tree

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
// Mock Obsidian's prepareFuzzySearch
2+
const mockPrepareFuzzySearch = jest.fn((query: string) => {
3+
return (target: string) => {
4+
// Simple mock implementation for testing
5+
return target.toLowerCase().includes(query.toLowerCase());
6+
};
7+
});
8+
9+
// Mock translations
10+
const mockT = jest.fn((key: string) => key);
11+
12+
jest.mock("obsidian", () => ({
13+
prepareFuzzySearch: mockPrepareFuzzySearch
14+
}));
15+
16+
jest.mock("../translations/helper", () => ({
17+
t: mockT
18+
}));
19+
20+
import { SettingsIndexer } from "../components/settings/SettingsIndexer";
21+
import { SETTINGS_METADATA } from "../data/settings-metadata";
22+
23+
describe("Settings Search Tests", () => {
24+
let indexer: SettingsIndexer;
25+
26+
beforeEach(() => {
27+
indexer = new SettingsIndexer();
28+
jest.clearAllMocks();
29+
});
30+
31+
describe("Settings Metadata", () => {
32+
test("should have valid metadata structure", () => {
33+
expect(SETTINGS_METADATA).toBeDefined();
34+
expect(Array.isArray(SETTINGS_METADATA)).toBe(true);
35+
expect(SETTINGS_METADATA.length).toBeGreaterThan(0);
36+
37+
// Check first few items have required fields
38+
const firstItem = SETTINGS_METADATA[0];
39+
expect(firstItem).toHaveProperty("id");
40+
expect(firstItem).toHaveProperty("tabId");
41+
expect(firstItem).toHaveProperty("name");
42+
expect(firstItem).toHaveProperty("translationKey");
43+
expect(firstItem).toHaveProperty("keywords");
44+
expect(Array.isArray(firstItem.keywords)).toBe(true);
45+
});
46+
47+
test("should have progress bar settings", () => {
48+
const progressBarMain = SETTINGS_METADATA.find(item => item.id === "progress-bar-main");
49+
expect(progressBarMain).toBeDefined();
50+
expect(progressBarMain?.name).toBe("Progress bar");
51+
expect(progressBarMain?.tabId).toBe("progress-bar");
52+
expect(progressBarMain?.keywords).toContain("progress");
53+
});
54+
55+
test("should have enable task filter setting", () => {
56+
const taskFilter = SETTINGS_METADATA.find(item => item.id === "enable-task-filter");
57+
expect(taskFilter).toBeDefined();
58+
expect(taskFilter?.name).toBe("Enable Task Filter");
59+
expect(taskFilter?.tabId).toBe("task-filter");
60+
});
61+
});
62+
63+
describe("SettingsIndexer", () => {
64+
test("should initialize correctly", () => {
65+
expect(indexer).toBeDefined();
66+
67+
// Should not be initialized yet
68+
const stats = indexer.getStats();
69+
expect(stats.itemCount).toBeGreaterThan(0);
70+
});
71+
72+
test("should build index on first search", () => {
73+
const results = indexer.search("progress");
74+
expect(mockT).toHaveBeenCalled();
75+
});
76+
77+
test("should search by name", () => {
78+
const results = indexer.search("progress");
79+
80+
console.log("Search results for 'progress':", results.length);
81+
results.forEach(result => {
82+
console.log(`- ${result.item.name} (${result.matchType}, score: ${result.score})`);
83+
});
84+
85+
expect(results.length).toBeGreaterThan(0);
86+
87+
// Should find progress bar settings
88+
const progressResult = results.find(result =>
89+
result.item.id === "progress-bar-main" ||
90+
result.item.name.toLowerCase().includes("progress")
91+
);
92+
expect(progressResult).toBeDefined();
93+
});
94+
95+
test("should search by description", () => {
96+
const results = indexer.search("toggle");
97+
98+
console.log("Search results for 'toggle':", results.length);
99+
results.forEach(result => {
100+
console.log(`- ${result.item.name} (${result.matchType}, score: ${result.score})`);
101+
if (result.item.description) {
102+
console.log(` Description: ${result.item.description.substring(0, 100)}...`);
103+
}
104+
});
105+
106+
expect(results.length).toBeGreaterThan(0);
107+
});
108+
109+
test("should search by keywords", () => {
110+
const results = indexer.search("checkbox");
111+
112+
console.log("Search results for 'checkbox':", results.length);
113+
results.forEach(result => {
114+
console.log(`- ${result.item.name} (${result.matchType}, score: ${result.score})`);
115+
console.log(` Keywords: ${result.item.keywords.join(", ")}`);
116+
});
117+
118+
expect(results.length).toBeGreaterThan(0);
119+
});
120+
121+
test("should prioritize name matches over description matches", () => {
122+
const results = indexer.search("filter");
123+
124+
console.log("Search results for 'filter' with scores:");
125+
results.forEach(result => {
126+
console.log(`- ${result.item.name} (${result.matchType}, score: ${result.score})`);
127+
});
128+
129+
expect(results.length).toBeGreaterThan(0);
130+
131+
// Find name matches and description matches
132+
const nameMatches = results.filter(r => r.matchType === "name");
133+
const descriptionMatches = results.filter(r => r.matchType === "description");
134+
135+
if (nameMatches.length > 0 && descriptionMatches.length > 0) {
136+
// Name matches should have higher scores
137+
const highestNameScore = Math.max(...nameMatches.map(r => r.score));
138+
const highestDescScore = Math.max(...descriptionMatches.map(r => r.score));
139+
expect(highestNameScore).toBeGreaterThan(highestDescScore);
140+
}
141+
});
142+
143+
test("should return empty results for empty query", () => {
144+
const results = indexer.search("");
145+
expect(results.length).toBe(0);
146+
});
147+
148+
test("should return empty results for very short query", () => {
149+
const results = indexer.search("a");
150+
expect(results.length).toBe(0);
151+
});
152+
153+
test("should return results for 2+ character query", () => {
154+
const results = indexer.search("pr");
155+
expect(results.length).toBeGreaterThan(0);
156+
});
157+
158+
test("should limit results", () => {
159+
const results = indexer.search("task");
160+
console.log(`Found ${results.length} results for 'task'`);
161+
expect(results.length).toBeLessThanOrEqual(10); // Default limit
162+
});
163+
164+
test("should handle case insensitive search", () => {
165+
const lowerResults = indexer.search("progress");
166+
const upperResults = indexer.search("PROGRESS");
167+
const mixedResults = indexer.search("Progress");
168+
169+
expect(lowerResults.length).toBe(upperResults.length);
170+
expect(lowerResults.length).toBe(mixedResults.length);
171+
});
172+
});
173+
174+
describe("Translation Integration", () => {
175+
test("should call translation function for setting names", () => {
176+
indexer.search("progress");
177+
178+
// Should have called t() for translating setting names
179+
expect(mockT).toHaveBeenCalled();
180+
181+
// Check if it was called with expected translation keys
182+
const calls = mockT.mock.calls.map((call: any) => call[0]);
183+
console.log("Translation calls:", calls.slice(0, 10)); // Show first 10 calls
184+
185+
expect(calls).toContain("Progress bar");
186+
});
187+
});
188+
189+
describe("Edge Cases", () => {
190+
test("should handle special characters in search", () => {
191+
const results = indexer.search("task-filter");
192+
expect(results.length).toBeGreaterThanOrEqual(0);
193+
});
194+
195+
test("should handle unicode characters", () => {
196+
const results = indexer.search("设置");
197+
expect(results.length).toBeGreaterThanOrEqual(0);
198+
});
199+
200+
test("should handle numbers in search", () => {
201+
const results = indexer.search("100");
202+
expect(results.length).toBeGreaterThanOrEqual(0);
203+
});
204+
});
205+
206+
describe("Performance", () => {
207+
test("should build index quickly", () => {
208+
const start = performance.now();
209+
indexer.initialize();
210+
const end = performance.now();
211+
212+
const buildTime = end - start;
213+
console.log(`Index build time: ${buildTime.toFixed(2)}ms`);
214+
expect(buildTime).toBeLessThan(50); // Should be under 50ms
215+
});
216+
217+
test("should search quickly", () => {
218+
indexer.initialize(); // Pre-initialize
219+
220+
const start = performance.now();
221+
const results = indexer.search("progress");
222+
const end = performance.now();
223+
224+
const searchTime = end - start;
225+
console.log(`Search time: ${searchTime.toFixed(2)}ms for ${results.length} results`);
226+
expect(searchTime).toBeLessThan(10); // Should be under 10ms
227+
});
228+
});
229+
230+
describe("Specific Setting Tests", () => {
231+
test("should find 'Enable Task Filter' setting", () => {
232+
const results = indexer.search("enable task filter");
233+
234+
console.log("Searching for 'enable task filter':");
235+
results.forEach(result => {
236+
console.log(`- ${result.item.name} (ID: ${result.item.id})`);
237+
});
238+
239+
const exactMatch = results.find(r => r.item.id === "enable-task-filter");
240+
expect(exactMatch).toBeDefined();
241+
});
242+
243+
test("should find progress bar settings", () => {
244+
const results = indexer.search("progress bar");
245+
246+
console.log("Searching for 'progress bar':");
247+
results.forEach(result => {
248+
console.log(`- ${result.item.name} (ID: ${result.item.id})`);
249+
});
250+
251+
const progressBarMain = results.find(r => r.item.id === "progress-bar-main");
252+
expect(progressBarMain).toBeDefined();
253+
expect(progressBarMain?.matchType).toBe("name");
254+
});
255+
256+
test("should find settings by partial name", () => {
257+
const results = indexer.search("workflow");
258+
259+
console.log("Searching for 'workflow':");
260+
results.forEach(result => {
261+
console.log(`- ${result.item.name} (ID: ${result.item.id})`);
262+
});
263+
264+
expect(results.length).toBeGreaterThan(0);
265+
266+
// Should find workflow-related settings
267+
const workflowSettings = results.filter(r =>
268+
r.item.name.toLowerCase().includes("workflow") ||
269+
r.item.tabId === "workflow"
270+
);
271+
expect(workflowSettings.length).toBeGreaterThan(0);
272+
});
273+
});
274+
});

src/components/settings/SettingsIndexer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export class SettingsIndexer {
3333
this.buildIndex();
3434

3535
const endTime = performance.now();
36-
console.log(`Settings index built in ${(endTime - startTime).toFixed(2)}ms`);
36+
console.log(`[SettingsIndexer] Index built in ${(endTime - startTime).toFixed(2)}ms with ${this.index.items.length} items`);
3737

3838
this.isInitialized = true;
3939
}
@@ -81,7 +81,7 @@ export class SettingsIndexer {
8181
this.initialize();
8282
}
8383

84-
if (!query || query.trim().length === 0) {
84+
if (!query || query.trim().length < 2) {
8585
return [];
8686
}
8787

src/components/settings/SettingsSearchComponent.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,19 +130,28 @@ export class SettingsSearchComponent {
130130
* 执行搜索
131131
*/
132132
private performSearch(query: string): void {
133+
console.log(`[SettingsSearch] Performing search for: "${query}"`);
134+
133135
if (query.length === 0) {
136+
console.log(`[SettingsSearch] Empty query, hiding results`);
134137
this.hideResults();
135138
return;
136139
}
137140

138141
// 最少输入2个字符开始搜索
139142
if (query.length < 2) {
143+
console.log(`[SettingsSearch] Query too short (${query.length} chars), skipping search`);
140144
return;
141145
}
142146

143147
this.currentResults = this.indexer.search(query, 8);
144148
this.selectedIndex = -1;
145149

150+
console.log(`[SettingsSearch] Found ${this.currentResults.length} results:`);
151+
this.currentResults.forEach((result, index) => {
152+
console.log(` ${index + 1}. ${result.item.name} (${result.matchType}, score: ${result.score})`);
153+
});
154+
146155
if (this.currentResults.length > 0) {
147156
this.renderResults();
148157
this.showResults();

0 commit comments

Comments
 (0)