piral-search 1.0.0-pre.2296 → 1.0.1-beta.5640
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +8 -2
- package/esm/Search.js +3 -3
- package/esm/Search.js.map +1 -1
- package/esm/SearchInput.js +2 -2
- package/esm/SearchInput.js.map +1 -1
- package/esm/actions.js +46 -49
- package/esm/actions.js.map +1 -1
- package/esm/components.d.ts +4 -5
- package/esm/components.js +3 -3
- package/esm/components.js.map +1 -1
- package/esm/create.js +37 -38
- package/esm/create.js.map +1 -1
- package/esm/default.js +3 -3
- package/esm/default.js.map +1 -1
- package/esm/types.d.ts +11 -3
- package/esm/useDebounce.d.ts +8 -0
- package/esm/useDebounce.js +17 -0
- package/esm/useDebounce.js.map +1 -0
- package/esm/useSearch.js +7 -6
- package/esm/useSearch.js.map +1 -1
- package/lib/Search.js +7 -6
- package/lib/Search.js.map +1 -1
- package/lib/SearchInput.js +6 -5
- package/lib/SearchInput.js.map +1 -1
- package/lib/actions.js +49 -52
- package/lib/actions.js.map +1 -1
- package/lib/components.d.ts +4 -5
- package/lib/components.js +4 -4
- package/lib/components.js.map +1 -1
- package/lib/create.js +43 -44
- package/lib/create.js.map +1 -1
- package/lib/default.js +8 -5
- package/lib/default.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/types.d.ts +11 -3
- package/lib/useDebounce.d.ts +8 -0
- package/lib/useDebounce.js +21 -0
- package/lib/useDebounce.js.map +1 -0
- package/lib/useSearch.js +8 -7
- package/lib/useSearch.js.map +1 -1
- package/package.json +27 -8
- package/piral-search.min.js +1 -0
- package/src/actions.test.ts +45 -44
- package/src/components.tsx +3 -5
- package/src/create.test.ts +16 -4
- package/src/create.ts +45 -23
- package/src/types.ts +12 -3
- package/src/useDebounce.test.ts +67 -0
- package/src/useDebounce.ts +19 -0
- package/src/useSearch.test.ts +17 -7
- package/src/useSearch.ts +2 -1
package/src/actions.test.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import create from 'zustand';
|
|
2
|
+
import { createListener } from 'piral-base';
|
|
3
|
+
import { createActions } from 'piral-core';
|
|
2
4
|
import { createActions as createSearchActions } from './actions';
|
|
3
|
-
import { createActions, createListener } from 'piral-core';
|
|
4
5
|
|
|
5
6
|
const state = {
|
|
6
7
|
search: {
|
|
@@ -14,7 +15,7 @@ const state = {
|
|
|
14
15
|
|
|
15
16
|
describe('Search Action Module', () => {
|
|
16
17
|
it('appendSearchResults cont appends new items with still loading', () => {
|
|
17
|
-
const state =
|
|
18
|
+
const state: any = create(() => ({
|
|
18
19
|
foo: 5,
|
|
19
20
|
search: {
|
|
20
21
|
input: 'yo',
|
|
@@ -23,11 +24,11 @@ describe('Search Action Module', () => {
|
|
|
23
24
|
items: ['c'],
|
|
24
25
|
},
|
|
25
26
|
},
|
|
26
|
-
});
|
|
27
|
+
}));
|
|
27
28
|
const ctx = createActions(state, createListener({}));
|
|
28
29
|
ctx.defineActions(createSearchActions());
|
|
29
30
|
ctx.appendSearchResults(['a', 'b'], false);
|
|
30
|
-
expect(
|
|
31
|
+
expect(state.getState()).toEqual({
|
|
31
32
|
foo: 5,
|
|
32
33
|
search: {
|
|
33
34
|
input: 'yo',
|
|
@@ -40,7 +41,7 @@ describe('Search Action Module', () => {
|
|
|
40
41
|
});
|
|
41
42
|
|
|
42
43
|
it('appendSearchResults stop appends new items without still loading', () => {
|
|
43
|
-
const state =
|
|
44
|
+
const state: any = create(() => ({
|
|
44
45
|
foo: [1, 2],
|
|
45
46
|
search: {
|
|
46
47
|
input: 'yo',
|
|
@@ -49,11 +50,11 @@ describe('Search Action Module', () => {
|
|
|
49
50
|
items: ['c'],
|
|
50
51
|
},
|
|
51
52
|
},
|
|
52
|
-
});
|
|
53
|
+
}));
|
|
53
54
|
const ctx = createActions(state, createListener({}));
|
|
54
55
|
ctx.defineActions(createSearchActions());
|
|
55
56
|
ctx.appendSearchResults(['a'], true);
|
|
56
|
-
expect(
|
|
57
|
+
expect(state.getState()).toEqual({
|
|
57
58
|
foo: [1, 2],
|
|
58
59
|
search: {
|
|
59
60
|
input: 'yo',
|
|
@@ -66,7 +67,7 @@ describe('Search Action Module', () => {
|
|
|
66
67
|
});
|
|
67
68
|
|
|
68
69
|
it('prependSearchResults cont prepends new items with still loading', () => {
|
|
69
|
-
const state =
|
|
70
|
+
const state: any = create(() => ({
|
|
70
71
|
foo: 5,
|
|
71
72
|
search: {
|
|
72
73
|
input: 'yo',
|
|
@@ -75,11 +76,11 @@ describe('Search Action Module', () => {
|
|
|
75
76
|
items: ['c'],
|
|
76
77
|
},
|
|
77
78
|
},
|
|
78
|
-
});
|
|
79
|
+
}));
|
|
79
80
|
const ctx = createActions(state, createListener({}));
|
|
80
81
|
ctx.defineActions(createSearchActions());
|
|
81
82
|
ctx.prependSearchResults(['a', 'b'], false);
|
|
82
|
-
expect(
|
|
83
|
+
expect(state.getState()).toEqual({
|
|
83
84
|
foo: 5,
|
|
84
85
|
search: {
|
|
85
86
|
input: 'yo',
|
|
@@ -92,7 +93,7 @@ describe('Search Action Module', () => {
|
|
|
92
93
|
});
|
|
93
94
|
|
|
94
95
|
it('prependSearchResults stop prepends new items without still loading', () => {
|
|
95
|
-
const state =
|
|
96
|
+
const state: any = create(() => ({
|
|
96
97
|
foo: [1, 2],
|
|
97
98
|
search: {
|
|
98
99
|
input: 'yo',
|
|
@@ -101,11 +102,11 @@ describe('Search Action Module', () => {
|
|
|
101
102
|
items: ['c'],
|
|
102
103
|
},
|
|
103
104
|
},
|
|
104
|
-
});
|
|
105
|
+
}));
|
|
105
106
|
const ctx = createActions(state, createListener({}));
|
|
106
107
|
ctx.defineActions(createSearchActions());
|
|
107
108
|
ctx.prependSearchResults(['a'], true);
|
|
108
|
-
expect(
|
|
109
|
+
expect(state.getState()).toEqual({
|
|
109
110
|
foo: [1, 2],
|
|
110
111
|
search: {
|
|
111
112
|
input: 'yo',
|
|
@@ -118,7 +119,7 @@ describe('Search Action Module', () => {
|
|
|
118
119
|
});
|
|
119
120
|
|
|
120
121
|
it('resetSearchResults cont resets the search results while still loading', () => {
|
|
121
|
-
const state =
|
|
122
|
+
const state: any = create(() => ({
|
|
122
123
|
foo: [1, 2],
|
|
123
124
|
search: {
|
|
124
125
|
input: 'yo',
|
|
@@ -127,11 +128,11 @@ describe('Search Action Module', () => {
|
|
|
127
128
|
items: ['c'],
|
|
128
129
|
},
|
|
129
130
|
},
|
|
130
|
-
});
|
|
131
|
+
}));
|
|
131
132
|
const ctx = createActions(state, createListener({}));
|
|
132
133
|
ctx.defineActions(createSearchActions());
|
|
133
134
|
ctx.resetSearchResults('yo', true);
|
|
134
|
-
expect(
|
|
135
|
+
expect(state.getState()).toEqual({
|
|
135
136
|
foo: [1, 2],
|
|
136
137
|
search: {
|
|
137
138
|
input: 'yo',
|
|
@@ -144,7 +145,7 @@ describe('Search Action Module', () => {
|
|
|
144
145
|
});
|
|
145
146
|
|
|
146
147
|
it('resetSearchResults stop resets the search results without loading', () => {
|
|
147
|
-
const state =
|
|
148
|
+
const state: any = create(() => ({
|
|
148
149
|
foo: 5,
|
|
149
150
|
search: {
|
|
150
151
|
input: 'yo y',
|
|
@@ -153,11 +154,11 @@ describe('Search Action Module', () => {
|
|
|
153
154
|
items: ['c'],
|
|
154
155
|
},
|
|
155
156
|
},
|
|
156
|
-
});
|
|
157
|
+
}));
|
|
157
158
|
const ctx = createActions(state, createListener({}));
|
|
158
159
|
ctx.defineActions(createSearchActions());
|
|
159
160
|
ctx.resetSearchResults('yo y', false);
|
|
160
|
-
expect(
|
|
161
|
+
expect(state.getState()).toEqual({
|
|
161
162
|
foo: 5,
|
|
162
163
|
search: {
|
|
163
164
|
input: 'yo y',
|
|
@@ -170,7 +171,7 @@ describe('Search Action Module', () => {
|
|
|
170
171
|
});
|
|
171
172
|
|
|
172
173
|
it('setSearchInput sets the input field accordingly', () => {
|
|
173
|
-
const state =
|
|
174
|
+
const state: any = create(() => ({
|
|
174
175
|
foo: 5,
|
|
175
176
|
search: {
|
|
176
177
|
input: 'yo y',
|
|
@@ -179,11 +180,11 @@ describe('Search Action Module', () => {
|
|
|
179
180
|
items: ['c'],
|
|
180
181
|
},
|
|
181
182
|
},
|
|
182
|
-
});
|
|
183
|
+
}));
|
|
183
184
|
const ctx = createActions(state, createListener({}));
|
|
184
185
|
ctx.defineActions(createSearchActions());
|
|
185
186
|
ctx.setSearchInput('test input');
|
|
186
|
-
expect(
|
|
187
|
+
expect(state.getState()).toEqual({
|
|
187
188
|
foo: 5,
|
|
188
189
|
search: {
|
|
189
190
|
input: 'test input',
|
|
@@ -197,11 +198,11 @@ describe('Search Action Module', () => {
|
|
|
197
198
|
|
|
198
199
|
it('immediately resets with loading false if some value is given but no provider found', () => {
|
|
199
200
|
state.search.input = 'foo';
|
|
200
|
-
const globalState =
|
|
201
|
+
const globalState: any = create(() => state);
|
|
201
202
|
const ctx = createActions(globalState, createListener({}));
|
|
202
203
|
ctx.defineActions(createSearchActions());
|
|
203
204
|
ctx.triggerSearch();
|
|
204
|
-
expect(
|
|
205
|
+
expect(globalState.getState().search.results.loading).toBe(false);
|
|
205
206
|
});
|
|
206
207
|
|
|
207
208
|
it('immediately resets with loading true if some value is given and a provider is found', () => {
|
|
@@ -213,38 +214,38 @@ describe('Search Action Module', () => {
|
|
|
213
214
|
clear() {},
|
|
214
215
|
cancel() {},
|
|
215
216
|
};
|
|
216
|
-
const globalState =
|
|
217
|
+
const globalState: any = create(() => state);
|
|
217
218
|
const ctx = createActions(globalState, createListener({}));
|
|
218
219
|
ctx.defineActions(createSearchActions());
|
|
219
220
|
ctx.triggerSearch();
|
|
220
|
-
expect(
|
|
221
|
+
expect(globalState.getState().search.results.loading).toBe(true);
|
|
221
222
|
});
|
|
222
223
|
|
|
223
224
|
it('immediately resets with loading false if no value is given implicitly', () => {
|
|
224
225
|
state.search.input = '';
|
|
225
|
-
const globalState =
|
|
226
|
+
const globalState: any = create(() => state);
|
|
226
227
|
const ctx = createActions(globalState, createListener({}));
|
|
227
228
|
ctx.defineActions(createSearchActions());
|
|
228
229
|
ctx.triggerSearch();
|
|
229
|
-
expect(
|
|
230
|
+
expect(globalState.getState().search.results.loading).toBe(false);
|
|
230
231
|
});
|
|
231
232
|
|
|
232
233
|
it('immediately resets with loading false if no value is given explicitly', () => {
|
|
233
|
-
const gs =
|
|
234
|
+
const gs: any = create(() => state);
|
|
234
235
|
const ctx = createActions(gs, createListener({}));
|
|
235
236
|
ctx.defineActions(createSearchActions());
|
|
236
237
|
const dispose = ctx.triggerSearch('');
|
|
237
238
|
dispose();
|
|
238
|
-
expect(
|
|
239
|
+
expect(gs.getState().search.results.loading).toBe(false);
|
|
239
240
|
});
|
|
240
241
|
|
|
241
242
|
it('immediately resets with loading false if no value is given explicitly though immediate', () => {
|
|
242
|
-
const gs =
|
|
243
|
+
const gs: any = create(() => state);
|
|
243
244
|
const ctx = createActions(gs, createListener({}));
|
|
244
245
|
ctx.defineActions(createSearchActions());
|
|
245
246
|
const dispose = ctx.triggerSearch('', true);
|
|
246
247
|
dispose();
|
|
247
|
-
expect(
|
|
248
|
+
expect(gs.getState().search.results.loading).toBe(false);
|
|
248
249
|
});
|
|
249
250
|
|
|
250
251
|
it('resets with loading true if no value is given explicitly', () => {
|
|
@@ -256,11 +257,11 @@ describe('Search Action Module', () => {
|
|
|
256
257
|
clear() {},
|
|
257
258
|
cancel() {},
|
|
258
259
|
};
|
|
259
|
-
const gs =
|
|
260
|
+
const gs: any = create(() => state);
|
|
260
261
|
const ctx = createActions(gs, createListener({}));
|
|
261
262
|
ctx.defineActions(createSearchActions());
|
|
262
263
|
ctx.triggerSearch('foo');
|
|
263
|
-
expect(
|
|
264
|
+
expect(gs.getState().search.results.loading).toBe(true);
|
|
264
265
|
});
|
|
265
266
|
|
|
266
267
|
it('walks over all search providers', () => {
|
|
@@ -272,7 +273,7 @@ describe('Search Action Module', () => {
|
|
|
272
273
|
foo: { search, clear, cancel },
|
|
273
274
|
bar: { search, clear, cancel },
|
|
274
275
|
};
|
|
275
|
-
const gs =
|
|
276
|
+
const gs: any = create(() => state);
|
|
276
277
|
const ctx = createActions(gs, createListener({}));
|
|
277
278
|
ctx.defineActions(createSearchActions());
|
|
278
279
|
ctx.triggerSearch();
|
|
@@ -290,7 +291,7 @@ describe('Search Action Module', () => {
|
|
|
290
291
|
cancel() {},
|
|
291
292
|
},
|
|
292
293
|
};
|
|
293
|
-
const gs =
|
|
294
|
+
const gs: any = create(() => state);
|
|
294
295
|
const ctx = createActions(gs, createListener({}));
|
|
295
296
|
ctx.defineActions(createSearchActions());
|
|
296
297
|
ctx.triggerSearch();
|
|
@@ -309,7 +310,7 @@ describe('Search Action Module', () => {
|
|
|
309
310
|
cancel,
|
|
310
311
|
},
|
|
311
312
|
};
|
|
312
|
-
const gs =
|
|
313
|
+
const gs: any = create(() => state);
|
|
313
314
|
const ctx = createActions(gs, createListener({}));
|
|
314
315
|
ctx.defineActions(createSearchActions());
|
|
315
316
|
ctx.triggerSearch();
|
|
@@ -329,7 +330,7 @@ describe('Search Action Module', () => {
|
|
|
329
330
|
cancel,
|
|
330
331
|
},
|
|
331
332
|
};
|
|
332
|
-
const gs =
|
|
333
|
+
const gs: any = create(() => state);
|
|
333
334
|
const ctx = createActions(gs, createListener({}));
|
|
334
335
|
ctx.defineActions(createSearchActions());
|
|
335
336
|
const dispose = ctx.triggerSearch();
|
|
@@ -350,7 +351,7 @@ describe('Search Action Module', () => {
|
|
|
350
351
|
cancel() {},
|
|
351
352
|
},
|
|
352
353
|
};
|
|
353
|
-
const gs =
|
|
354
|
+
const gs: any = create(() => state);
|
|
354
355
|
const ctx = createActions(gs, createListener({}));
|
|
355
356
|
ctx.defineActions(createSearchActions());
|
|
356
357
|
ctx.triggerSearch();
|
|
@@ -359,17 +360,17 @@ describe('Search Action Module', () => {
|
|
|
359
360
|
});
|
|
360
361
|
|
|
361
362
|
it('registerSearchProvider and unregisterSearchProvider', () => {
|
|
362
|
-
const state =
|
|
363
|
+
const state: any = create(() => ({
|
|
363
364
|
foo: 5,
|
|
364
365
|
registry: {
|
|
365
366
|
foo: 5,
|
|
366
367
|
searchProviders: {},
|
|
367
368
|
},
|
|
368
|
-
});
|
|
369
|
+
}));
|
|
369
370
|
const ctx = createActions(state, createListener({}));
|
|
370
371
|
ctx.defineActions(createSearchActions());
|
|
371
372
|
ctx.registerSearchProvider('foo', 10 as any);
|
|
372
|
-
expect(
|
|
373
|
+
expect(state.getState()).toEqual({
|
|
373
374
|
foo: 5,
|
|
374
375
|
registry: {
|
|
375
376
|
foo: 5,
|
|
@@ -379,7 +380,7 @@ describe('Search Action Module', () => {
|
|
|
379
380
|
},
|
|
380
381
|
});
|
|
381
382
|
ctx.unregisterSearchProvider('foo');
|
|
382
|
-
expect(
|
|
383
|
+
expect(state.getState()).toEqual({
|
|
383
384
|
foo: 5,
|
|
384
385
|
registry: {
|
|
385
386
|
foo: 5,
|
package/src/components.tsx
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
1
|
import { getPiralComponent } from 'piral-core';
|
|
3
|
-
import { SearchContainerProps, SearchInputProps, SearchResultProps } from './types';
|
|
4
2
|
|
|
5
|
-
export const PiralSearchContainer
|
|
6
|
-
export const PiralSearchInput
|
|
7
|
-
export const PiralSearchResult
|
|
3
|
+
export const PiralSearchContainer = getPiralComponent('SearchContainer');
|
|
4
|
+
export const PiralSearchInput = getPiralComponent('SearchInput');
|
|
5
|
+
export const PiralSearchResult = getPiralComponent('SearchResult');
|
package/src/create.test.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import create from 'zustand';
|
|
2
2
|
import { createSearchApi } from './create';
|
|
3
3
|
|
|
4
4
|
function createMockContainer() {
|
|
5
|
-
const state =
|
|
5
|
+
const state = create(() => ({
|
|
6
|
+
registry: {
|
|
7
|
+
wrappers: {},
|
|
8
|
+
extensions: {},
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
6
11
|
return {
|
|
7
12
|
context: {
|
|
8
13
|
on: jest.fn(),
|
|
@@ -10,11 +15,18 @@ function createMockContainer() {
|
|
|
10
15
|
emit: jest.fn(),
|
|
11
16
|
defineActions() {},
|
|
12
17
|
state,
|
|
18
|
+
readState(cb) {
|
|
19
|
+
return cb(state.getState());
|
|
20
|
+
},
|
|
13
21
|
dispatch(update) {
|
|
14
|
-
|
|
22
|
+
state.setState(update(state.getState()));
|
|
23
|
+
},
|
|
24
|
+
} as any,
|
|
25
|
+
api: {
|
|
26
|
+
meta: {
|
|
27
|
+
name: '',
|
|
15
28
|
},
|
|
16
29
|
} as any,
|
|
17
|
-
api: {} as any,
|
|
18
30
|
};
|
|
19
31
|
}
|
|
20
32
|
|
package/src/create.ts
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
|
-
import { isfunc } from 'piral-base';
|
|
2
1
|
import { ReactChild, isValidElement, createElement } from 'react';
|
|
3
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
isfunc,
|
|
4
|
+
buildName,
|
|
5
|
+
PiralPlugin,
|
|
6
|
+
Dict,
|
|
7
|
+
withApi,
|
|
8
|
+
PiletApi,
|
|
9
|
+
GlobalStateContext,
|
|
10
|
+
withAll,
|
|
11
|
+
GlobalState,
|
|
12
|
+
withRootExtension,
|
|
13
|
+
} from 'piral-core';
|
|
4
14
|
import { createActions } from './actions';
|
|
5
15
|
import { DefaultContainer, DefaultInput, DefaultResult } from './default';
|
|
16
|
+
import { Search } from './Search';
|
|
17
|
+
import { SearchInput } from './SearchInput';
|
|
6
18
|
import {
|
|
7
19
|
PiletSearchApi,
|
|
8
20
|
SearchSettings,
|
|
@@ -86,7 +98,7 @@ function getSearchProviders(providers: Array<InitialSearchProvider>) {
|
|
|
86
98
|
}
|
|
87
99
|
|
|
88
100
|
function toChild(content: SearchResultType, api: PiletApi, context: GlobalStateContext): ReactChild {
|
|
89
|
-
if (typeof content === 'string' || isValidElement(content)) {
|
|
101
|
+
if (typeof content === 'string' || isValidElement<any>(content)) {
|
|
90
102
|
return content;
|
|
91
103
|
} else {
|
|
92
104
|
const component = withApi(context, content, api, 'extension');
|
|
@@ -103,6 +115,29 @@ function wrapResults(
|
|
|
103
115
|
return results.map((item) => toChild(item, api, context));
|
|
104
116
|
}
|
|
105
117
|
|
|
118
|
+
function withSearch(searchProviders: Dict<SearchProviderRegistration>, query: string, items: Array<ReactChild>) {
|
|
119
|
+
return (state: GlobalState): GlobalState => ({
|
|
120
|
+
...state,
|
|
121
|
+
components: {
|
|
122
|
+
SearchContainer: DefaultContainer,
|
|
123
|
+
SearchInput: DefaultInput,
|
|
124
|
+
SearchResult: DefaultResult,
|
|
125
|
+
...state.components,
|
|
126
|
+
},
|
|
127
|
+
registry: {
|
|
128
|
+
...state.registry,
|
|
129
|
+
searchProviders,
|
|
130
|
+
},
|
|
131
|
+
search: {
|
|
132
|
+
input: query,
|
|
133
|
+
results: {
|
|
134
|
+
loading: false,
|
|
135
|
+
items,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
106
141
|
/**
|
|
107
142
|
* Creates new Pilet API extensions for search and filtering.
|
|
108
143
|
*/
|
|
@@ -112,26 +147,13 @@ export function createSearchApi(config: SearchConfig = {}): PiralPlugin<PiletSea
|
|
|
112
147
|
return (context) => {
|
|
113
148
|
context.defineActions(createActions(actionConfig));
|
|
114
149
|
|
|
115
|
-
context.dispatch(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
SearchInput
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
},
|
|
123
|
-
registry: {
|
|
124
|
-
...state.registry,
|
|
125
|
-
searchProviders: getSearchProviders(providers),
|
|
126
|
-
},
|
|
127
|
-
search: {
|
|
128
|
-
input: query,
|
|
129
|
-
results: {
|
|
130
|
-
loading: false,
|
|
131
|
-
items: results,
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
}));
|
|
150
|
+
context.dispatch(
|
|
151
|
+
withAll(
|
|
152
|
+
withSearch(getSearchProviders(providers), query, results),
|
|
153
|
+
withRootExtension('piral-search', Search),
|
|
154
|
+
withRootExtension('piral-search-input', SearchInput),
|
|
155
|
+
),
|
|
156
|
+
);
|
|
135
157
|
|
|
136
158
|
return (api, target) => {
|
|
137
159
|
const pilet = target.name;
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ReactChild, ComponentType, ReactElement } from 'react';
|
|
2
|
-
import {
|
|
1
|
+
import type { ReactChild, ComponentType, ReactElement, ReactNode } from 'react';
|
|
2
|
+
import type {
|
|
3
3
|
Dict,
|
|
4
4
|
Disposable,
|
|
5
5
|
PiletApi,
|
|
@@ -90,6 +90,10 @@ export interface SearchContainerProps {
|
|
|
90
90
|
* Gets if the results are still gathered.
|
|
91
91
|
*/
|
|
92
92
|
loading: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* The search to display.
|
|
95
|
+
*/
|
|
96
|
+
children?: ReactNode;
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
export interface SearchInputProps {
|
|
@@ -99,7 +103,12 @@ export interface SearchInputProps {
|
|
|
99
103
|
|
|
100
104
|
export interface SearchResultComponentProps extends BaseComponentProps {}
|
|
101
105
|
|
|
102
|
-
export interface SearchResultProps {
|
|
106
|
+
export interface SearchResultProps {
|
|
107
|
+
/**
|
|
108
|
+
* The search results to display.
|
|
109
|
+
*/
|
|
110
|
+
children?: ReactNode;
|
|
111
|
+
}
|
|
103
112
|
|
|
104
113
|
export type SearchResultType = string | ReactElement<any> | AnyComponent<SearchResultComponentProps>;
|
|
105
114
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useDebounce } from './useDebounce';
|
|
3
|
+
|
|
4
|
+
jest.mock('react');
|
|
5
|
+
|
|
6
|
+
describe('Debounce Hook Module', () => {
|
|
7
|
+
it('just returns initial value if nothing has been changed', () => {
|
|
8
|
+
const usedEffect = jest.fn();
|
|
9
|
+
const usedState = jest.fn((value) => [value]);
|
|
10
|
+
(React as any).useState = usedState;
|
|
11
|
+
(React as any).useEffect = usedEffect;
|
|
12
|
+
const result = useDebounce('foo');
|
|
13
|
+
expect(usedEffect).toHaveBeenCalled();
|
|
14
|
+
expect(usedState).toHaveBeenCalled();
|
|
15
|
+
expect(result).toBe('foo');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('invokes useEffect immediately, but does not set value immediately', () => {
|
|
19
|
+
const usedEffect = jest.fn((fn) => fn());
|
|
20
|
+
const setValue = jest.fn();
|
|
21
|
+
const usedState = jest.fn((value) => [value, setValue]);
|
|
22
|
+
(React as any).useState = usedState;
|
|
23
|
+
(React as any).useEffect = usedEffect;
|
|
24
|
+
useDebounce('foo');
|
|
25
|
+
expect(setValue).not.toHaveBeenCalled();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('invokes useEffect immediately, but sets value immediately if 0', () => {
|
|
29
|
+
jest.useFakeTimers();
|
|
30
|
+
const usedEffect = jest.fn((fn) => fn());
|
|
31
|
+
const setValue = jest.fn();
|
|
32
|
+
const usedState = jest.fn((value) => [value, setValue]);
|
|
33
|
+
(React as any).useState = usedState;
|
|
34
|
+
(React as any).useEffect = usedEffect;
|
|
35
|
+
useDebounce('foo', 0);
|
|
36
|
+
jest.advanceTimersByTime(0);
|
|
37
|
+
expect(setValue).toHaveBeenCalled();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('invokes useEffect immediately, but sets value after wait time', () => {
|
|
41
|
+
jest.useFakeTimers();
|
|
42
|
+
const usedEffect = jest.fn((fn) => fn());
|
|
43
|
+
const setValue = jest.fn();
|
|
44
|
+
const usedState = jest.fn((value) => [value, setValue]);
|
|
45
|
+
(React as any).useState = usedState;
|
|
46
|
+
(React as any).useEffect = usedEffect;
|
|
47
|
+
expect(setValue).not.toHaveBeenCalled();
|
|
48
|
+
useDebounce('foo', 300);
|
|
49
|
+
jest.advanceTimersByTime(300);
|
|
50
|
+
expect(setValue).toHaveBeenCalled();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('invokes useEffect immediately and resets timer if needed', () => {
|
|
54
|
+
jest.useFakeTimers();
|
|
55
|
+
const usedEffect = jest.fn((fn) => fn());
|
|
56
|
+
const setValue = jest.fn();
|
|
57
|
+
const usedState = jest.fn((value) => [value, setValue]);
|
|
58
|
+
(React as any).useState = usedState;
|
|
59
|
+
(React as any).useEffect = usedEffect;
|
|
60
|
+
expect(setValue).not.toHaveBeenCalled();
|
|
61
|
+
useDebounce('foo', 300);
|
|
62
|
+
jest.advanceTimersByTime(250);
|
|
63
|
+
usedEffect.mock.results[0].value();
|
|
64
|
+
jest.advanceTimersByTime(50);
|
|
65
|
+
expect(setValue).not.toHaveBeenCalled();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook that returns the debounced (i.e., delayed) value.
|
|
5
|
+
* Useful when user input should not fire immediately, but rather
|
|
6
|
+
* only after a certain timespan without any changes passed.
|
|
7
|
+
* @param value The value to consider.
|
|
8
|
+
* @param delay The timespan to pass before applying the value.
|
|
9
|
+
*/
|
|
10
|
+
export function useDebounce<T>(value: T, delay = 300) {
|
|
11
|
+
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const handler = setTimeout(() => setDebouncedValue(value), delay);
|
|
15
|
+
return () => clearTimeout(handler);
|
|
16
|
+
}, [value, delay]);
|
|
17
|
+
|
|
18
|
+
return debouncedValue;
|
|
19
|
+
}
|
package/src/useSearch.test.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import * as piralCore from 'piral-core';
|
|
3
|
-
import { useSearch } from './useSearch';
|
|
4
2
|
|
|
5
3
|
const state = {
|
|
6
4
|
search: {
|
|
@@ -11,19 +9,28 @@ const state = {
|
|
|
11
9
|
},
|
|
12
10
|
};
|
|
13
11
|
|
|
14
|
-
(piralCore as any).useGlobalState = (select: any) => select(state);
|
|
15
|
-
|
|
16
|
-
(piralCore as any).useDebounce = (value) => value;
|
|
17
|
-
|
|
18
12
|
const availableActions = {
|
|
19
13
|
setSearchInput: jest.fn(),
|
|
20
14
|
triggerSearch: jest.fn(),
|
|
21
15
|
};
|
|
22
16
|
|
|
23
|
-
|
|
17
|
+
const useGlobalState = jest.fn((select: any) => select(state));
|
|
18
|
+
const useActions = jest.fn(() => availableActions);
|
|
19
|
+
const useDebounce = jest.fn((value) => value);
|
|
20
|
+
|
|
21
|
+
jest.mock('piral-core', () => ({
|
|
22
|
+
...jest.requireActual('piral-core'),
|
|
23
|
+
useGlobalState,
|
|
24
|
+
useActions,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
jest.mock('./useDebounce.ts', () => ({
|
|
28
|
+
useDebounce,
|
|
29
|
+
}));
|
|
24
30
|
|
|
25
31
|
describe('Search Hook Module', () => {
|
|
26
32
|
it('just returns current input value', () => {
|
|
33
|
+
const { useSearch } = require('./useSearch');
|
|
27
34
|
const usedEffect = jest.fn();
|
|
28
35
|
(React as any).useEffect = usedEffect;
|
|
29
36
|
(React as any).useRef = (current) => ({ current });
|
|
@@ -32,6 +39,7 @@ describe('Search Hook Module', () => {
|
|
|
32
39
|
});
|
|
33
40
|
|
|
34
41
|
it('sets the value using the action', () => {
|
|
42
|
+
const { useSearch } = require('./useSearch');
|
|
35
43
|
const usedEffect = jest.fn();
|
|
36
44
|
(React as any).useEffect = usedEffect;
|
|
37
45
|
(React as any).useRef = (current) => ({ current });
|
|
@@ -42,6 +50,7 @@ describe('Search Hook Module', () => {
|
|
|
42
50
|
});
|
|
43
51
|
|
|
44
52
|
it('triggers the search without immediate mode', () => {
|
|
53
|
+
const { useSearch } = require('./useSearch');
|
|
45
54
|
const usedEffect = jest.fn((fn) => fn());
|
|
46
55
|
(React as any).useEffect = usedEffect;
|
|
47
56
|
(React as any).useRef = (current) => ({ current });
|
|
@@ -51,6 +60,7 @@ describe('Search Hook Module', () => {
|
|
|
51
60
|
});
|
|
52
61
|
|
|
53
62
|
it('cancels the current search', () => {
|
|
63
|
+
const { useSearch } = require('./useSearch');
|
|
54
64
|
const usedEffect = jest.fn((fn) => fn());
|
|
55
65
|
const cancel = jest.fn();
|
|
56
66
|
(React as any).useEffect = usedEffect;
|
package/src/useSearch.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react';
|
|
2
|
-
import { useGlobalState,
|
|
2
|
+
import { useGlobalState, useActions, Disposable } from 'piral-core';
|
|
3
|
+
import { useDebounce } from './useDebounce';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Hook that yields the possibility of searching in Piral.
|