Skip to content

Commit ff2ba7c

Browse files
SkyZeroZxpkozlowski-opensource
authored andcommitted
docs(docs-infra): replace wait with debounce for search
Replaces custom search debounce with `debounced`
1 parent 7234432 commit ff2ba7c

4 files changed

Lines changed: 64 additions & 30 deletions

File tree

adev/shared-docs/components/search-dialog/search-dialog.component.spec.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {Router, provideRouter} from '@angular/router';
1414
import {SearchDialog} from './search-dialog.component';
1515
import {ENVIRONMENT, WINDOW} from '../../providers';
1616
import {ALGOLIA_CLIENT, Search} from '../../services';
17-
import {FakeEventTarget} from '../../testing/index';
17+
import {FakeEventTarget, timeout, useAutoTick} from '../../testing/index';
1818
import {AlgoliaIcon} from '../algolia-icon/algolia-icon.component';
1919
import {SearchResult} from '../../interfaces';
2020

@@ -27,8 +27,10 @@ describe('SearchDialog', () => {
2727

2828
let search: Search;
2929

30+
useAutoTick();
31+
3032
beforeEach(async () => {
31-
searchResults.and.returnValue([]);
33+
searchResults.and.returnValue(Promise.resolve({results: [{hits: []}]}));
3234

3335
TestBed.configureTestingModule({
3436
imports: [SearchDialog],
@@ -55,6 +57,9 @@ describe('SearchDialog', () => {
5557
// Fire the request
5658
TestBed.inject(ApplicationRef).tick();
5759

60+
// The delay from debounced (200ms)
61+
await timeout(300);
62+
5863
// Wait for the resource to resolve
5964
await TestBed.inject(ApplicationRef).whenStable();
6065

@@ -85,6 +90,9 @@ describe('SearchDialog', () => {
8590
// Fire the request
8691
TestBed.inject(ApplicationRef).tick();
8792

93+
// The delay from debounced (200ms)
94+
await timeout(300);
95+
8896
// Wait for the resource to resolve
8997
await TestBed.inject(ApplicationRef).whenStable();
9098

@@ -96,7 +104,7 @@ describe('SearchDialog', () => {
96104
});
97105

98106
it('should display `Start typing to see results` message when there are no provided query', () => {
99-
searchResults.and.returnValue(undefined);
107+
searchResults.and.returnValue(Promise.resolve(undefined));
100108

101109
const startTypingContainer = fixture.debugElement.query(
102110
By.css('.docs-search-results__start-typing'),
@@ -112,6 +120,9 @@ describe('SearchDialog', () => {
112120
// Fire the request
113121
TestBed.inject(ApplicationRef).tick();
114122

123+
// The delay from debounced (200ms)
124+
await timeout(300);
125+
115126
// Wait for the resource to resolve
116127
await TestBed.inject(ApplicationRef).whenStable();
117128

adev/shared-docs/services/search.service.ts

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
Injectable,
1111
InjectionToken,
1212
Provider,
13+
debounced,
1314
inject,
1415
linkedSignal,
1516
resource,
@@ -48,13 +49,11 @@ export class Search {
4849
private readonly config = inject(ENVIRONMENT);
4950
private readonly client = inject(ALGOLIA_CLIENT);
5051

52+
debounceParams = debounced(this.searchQuery, SEARCH_DELAY);
53+
5154
readonly resultsResource = resource({
52-
params: () => this.searchQuery() || undefined, // coerces empty string to undefined
53-
loader: async ({params: query, abortSignal}) => {
54-
// Until we have a better alternative we debounce by awaiting for a short delay.
55-
await wait(SEARCH_DELAY, abortSignal);
56-
return this.searchWithQuery(query);
57-
},
55+
params: () => this.debounceParams.value() || undefined, // coerces empty string to undefined
56+
loader: async ({params}) => this.searchWithQuery(params),
5857
});
5958

6059
readonly searchResults = linkedSignal<SearchResultItem[] | undefined, SearchResultItem[]>({
@@ -223,27 +222,6 @@ function matched(snippet: SnippetResult | undefined): boolean {
223222
return snippet?.matchLevel !== undefined && snippet.matchLevel !== 'none';
224223
}
225224

226-
/**
227-
* Temporary helper to implement the debounce functionality on the search resource
228-
*/
229-
function wait(ms: number, signal: AbortSignal): Promise<void> {
230-
return new Promise<void>((resolve, reject) => {
231-
let timeout: ReturnType<typeof setTimeout> | undefined;
232-
233-
const onAbort = () => {
234-
clearTimeout(timeout);
235-
reject(new Error('Operation aborted'));
236-
};
237-
238-
timeout = setTimeout(() => {
239-
signal.removeEventListener('abort', onAbort);
240-
resolve();
241-
}, ms);
242-
243-
signal.addEventListener('abort', onAbort, {once: true});
244-
});
245-
}
246-
247225
function extractPackageNameFromUrl(url: string): string | null {
248226
const extractedSegment = url.match(/\/api\/(.*)\/.*#?/);
249227
if (extractedSegment == null) {

adev/shared-docs/testing/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ ts_project(
2121
"index.ts",
2222
],
2323
),
24+
tsconfig = "//adev/shared-docs:tsconfig_test",
2425
deps = [
2526
"//adev:node_modules/@angular/core",
27+
"//adev:node_modules/@types/jasmine",
2628
"//adev:node_modules/@webcontainer/api",
2729
],
2830
)

adev/shared-docs/testing/testing-helper.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,46 @@ export class FakeWebContainerProcess implements WebContainerProcess {
181181
kill(): void {}
182182
resize(dimensions: {cols: number; rows: number}): void {}
183183
}
184+
185+
// Copy from utils in packages/private/testing
186+
187+
/**
188+
* Returns a promise that resolves after the specified time.
189+
*
190+
* @param ms - Time to wait in milliseconds. Defaults to 0.
191+
*
192+
* @example
193+
* ```ts
194+
* await timeout(100); // Wait 100ms
195+
* ```
196+
*/
197+
export async function timeout(ms?: number): Promise<void> {
198+
return new Promise((resolve) => {
199+
setTimeout(resolve, ms);
200+
});
201+
}
202+
203+
/**
204+
* Installs Jasmine's fake clock with auto-tick enabled for all tests in the describe block.
205+
* Call at the top level of a describe block to automatically advance time for async operations.
206+
*
207+
* @example
208+
* ```ts
209+
* describe('MyComponent', () => {
210+
* useAutoTick();
211+
*
212+
* it('should handle timers', () => {
213+
* // setTimeout, setInterval, etc. will execute synchronously
214+
* });
215+
* });
216+
* ```
217+
*/
218+
export function useAutoTick() {
219+
beforeEach(() => {
220+
jasmine.clock().install();
221+
jasmine.clock().autoTick();
222+
});
223+
afterEach(() => {
224+
jasmine.clock().uninstall();
225+
});
226+
}

0 commit comments

Comments
 (0)