Skip to content

Commit aa5223b

Browse files
committed
Add on error catch
1 parent ef8b01a commit aa5223b

File tree

7 files changed

+147
-19
lines changed

7 files changed

+147
-19
lines changed

dest/simple-jekyll-search.js

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@
242242
exclude: [],
243243
onSearch: () => {
244244
},
245+
onError: (error) => console.error("SimpleJekyllSearch error:", error),
245246
fuzzy: false
246247
// Deprecated, use strategy: 'fuzzy' instead
247248
};
@@ -401,6 +402,9 @@
401402
let SimpleJekyllSearch$1 = class SimpleJekyllSearch {
402403
constructor() {
403404
this.debounceTimerHandle = null;
405+
this.eventHandler = null;
406+
this.pendingRequest = null;
407+
this.isInitialized = false;
404408
this.options = { ...DEFAULT_OPTIONS };
405409
this.repository = new Repository();
406410
this.optionsValidator = new OptionsValidator({
@@ -436,15 +440,28 @@
436440
});
437441
}
438442
registerInput() {
439-
this.options.searchInput.addEventListener("input", (e) => {
440-
const inputEvent = e;
441-
if (!WHITELISTED_KEYS.has(inputEvent.key)) {
442-
this.emptyResultsContainer();
443-
this.debounce(() => {
444-
this.search(e.target.value);
445-
}, this.options.debounceTime ?? null);
443+
this.eventHandler = (e) => {
444+
var _a, _b;
445+
try {
446+
const inputEvent = e;
447+
if (!WHITELISTED_KEYS.has(inputEvent.key)) {
448+
this.emptyResultsContainer();
449+
this.debounce(() => {
450+
var _a2, _b2;
451+
try {
452+
this.search(e.target.value);
453+
} catch (searchError) {
454+
console.error("Search error:", searchError);
455+
(_b2 = (_a2 = this.options).onError) == null ? void 0 : _b2.call(_a2, searchError);
456+
}
457+
}, this.options.debounceTime ?? null);
458+
}
459+
} catch (error) {
460+
console.error("Input handler error:", error);
461+
(_b = (_a = this.options).onError) == null ? void 0 : _b.call(_a, error);
446462
}
447-
});
463+
};
464+
this.options.searchInput.addEventListener("input", this.eventHandler);
448465
}
449466
search(query) {
450467
var _a, _b;

dest/simple-jekyll-search.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/assets/js/simple-jekyll-search.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/SimpleJekyllSearch.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ class SimpleJekyllSearch {
1111
private repository: Repository;
1212
private optionsValidator: OptionsValidator;
1313
private debounceTimerHandle: NodeJS.Timeout | null = null;
14+
private eventHandler: ((e: Event) => void) | null = null;
15+
private pendingRequest: XMLHttpRequest | null = null;
16+
private isInitialized: boolean = false;
1417

1518
constructor() {
1619
this.options = { ...DEFAULT_OPTIONS };
@@ -54,15 +57,27 @@ class SimpleJekyllSearch {
5457
}
5558

5659
private registerInput(): void {
57-
this.options.searchInput.addEventListener('input', (e: Event) => {
58-
const inputEvent = e as KeyboardEvent;
59-
if (!WHITELISTED_KEYS.has(inputEvent.key)) {
60-
this.emptyResultsContainer();
61-
this.debounce(() => {
62-
this.search((e.target as HTMLInputElement).value);
63-
}, this.options.debounceTime ?? null);
60+
this.eventHandler = (e: Event) => {
61+
try {
62+
const inputEvent = e as KeyboardEvent;
63+
if (!WHITELISTED_KEYS.has(inputEvent.key)) {
64+
this.emptyResultsContainer();
65+
this.debounce(() => {
66+
try {
67+
this.search((e.target as HTMLInputElement).value);
68+
} catch (searchError) {
69+
console.error('Search error:', searchError);
70+
this.options.onError?.(searchError as Error);
71+
}
72+
}, this.options.debounceTime ?? null);
73+
}
74+
} catch (error) {
75+
console.error('Input handler error:', error);
76+
this.options.onError?.(error as Error);
6477
}
65-
});
78+
};
79+
80+
this.options.searchInput.addEventListener('input', this.eventHandler);
6681
}
6782

6883
public search(query: string): void {

src/utils/default.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const DEFAULT_OPTIONS: Required<SearchOptions> = {
1515
debounceTime: null,
1616
exclude: [],
1717
onSearch: () => {},
18+
onError: (error: Error) => console.error('SimpleJekyllSearch error:', error),
1819
fuzzy: false // Deprecated, use strategy: 'fuzzy' instead
1920
};
2021

src/utils/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export interface SearchOptions extends Omit<RepositoryOptions, 'searchStrategy'>
4646
noResultsText?: string;
4747
debounceTime?: number | null;
4848
onSearch?: () => void;
49+
onError?: (error: Error) => void;
4950
}
5051

5152
export interface SimpleJekyllSearchInstance {

tests/SimpleJekyllSearch.test.ts

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { JSDOM } from 'jsdom';
2-
import { beforeEach, describe, expect, it } from 'vitest';
2+
import { beforeEach, describe, expect, it, vi } from 'vitest';
33
import SimpleJekyllSearch from '../src/SimpleJekyllSearch';
44
import { SearchData, SearchOptions } from '../src/utils/types';
55

@@ -144,4 +144,98 @@ describe('SimpleJekyllSearch', () => {
144144
expect(resultsContainer.innerHTML).toContain('Test Post');
145145
});
146146
});
147+
148+
describe('error handling', () => {
149+
beforeEach(() => {
150+
mockOptions.json = mockSearchData;
151+
});
152+
153+
it('should call onError callback when provided', async () => {
154+
const onErrorSpy = vi.fn();
155+
const optionsWithErrorHandler = { ...mockOptions, onError: onErrorSpy };
156+
157+
searchInstance.init(optionsWithErrorHandler);
158+
159+
const input = mockOptions.searchInput;
160+
input.value = 'test';
161+
input.dispatchEvent(new dom.window.KeyboardEvent('input', { key: 't' }));
162+
163+
await new Promise(resolve => setTimeout(resolve, mockOptions.debounceTime! + 10));
164+
165+
expect(onErrorSpy).not.toHaveBeenCalled();
166+
});
167+
168+
it('should handle malformed search data gracefully', async () => {
169+
const onErrorSpy = vi.fn();
170+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
171+
172+
const malformedData = [
173+
{ title: 'Valid Post', url: '/valid' },
174+
{ title: null, url: undefined },
175+
{ title: 'Another Valid Post', url: '/another' }
176+
];
177+
178+
const optionsWithMalformedData = {
179+
...mockOptions,
180+
json: malformedData as any,
181+
onError: onErrorSpy
182+
};
183+
184+
expect(() => searchInstance.init(optionsWithMalformedData)).not.toThrow();
185+
186+
const input = mockOptions.searchInput;
187+
input.value = 'Valid';
188+
input.dispatchEvent(new dom.window.KeyboardEvent('input', { key: 'V' }));
189+
190+
await new Promise(resolve => setTimeout(resolve, mockOptions.debounceTime! + 10));
191+
192+
expect(onErrorSpy).toHaveBeenCalledWith(expect.any(Error));
193+
consoleErrorSpy.mockRestore();
194+
});
195+
196+
it('should handle missing DOM elements gracefully', async () => {
197+
const onErrorSpy = vi.fn();
198+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
199+
200+
const optionsWithMissingElement = {
201+
...mockOptions,
202+
searchInput: null as any,
203+
onError: onErrorSpy
204+
};
205+
206+
expect(() => searchInstance.init(optionsWithMissingElement)).toThrow();
207+
consoleErrorSpy.mockRestore();
208+
});
209+
210+
it('should use default error handler when onError not provided', async () => {
211+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
212+
213+
searchInstance.init(mockOptions);
214+
215+
const input = mockOptions.searchInput;
216+
input.value = 'test';
217+
input.dispatchEvent(new dom.window.KeyboardEvent('input', { key: 't' }));
218+
219+
await new Promise(resolve => setTimeout(resolve, mockOptions.debounceTime! + 10));
220+
221+
expect(consoleErrorSpy).not.toHaveBeenCalled();
222+
consoleErrorSpy.mockRestore();
223+
});
224+
225+
it('should handle invalid search queries gracefully', async () => {
226+
const onErrorSpy = vi.fn();
227+
const optionsWithErrorHandler = { ...mockOptions, onError: onErrorSpy };
228+
229+
searchInstance.init(optionsWithErrorHandler);
230+
231+
const input = mockOptions.searchInput;
232+
input.value = 'a'.repeat(10000);
233+
input.dispatchEvent(new dom.window.KeyboardEvent('input', { key: 'a' }));
234+
235+
await new Promise(resolve => setTimeout(resolve, mockOptions.debounceTime! + 10));
236+
237+
expect(mockOptions.resultsContainer.innerHTML).toContain('No results found');
238+
expect(onErrorSpy).not.toHaveBeenCalled();
239+
});
240+
});
147241
});

0 commit comments

Comments
 (0)