@promoboxx/use-filter 2.0.0 → 2.0.1
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/dist/cjs/_virtual/_rolldown/runtime.js +29 -0
- package/dist/cjs/lib/buildDefaultFilterInfo.d.ts +5 -0
- package/dist/cjs/lib/buildDefaultFilterInfo.js +27 -0
- package/dist/cjs/lib/getOffsetFromPage.d.ts +3 -0
- package/dist/cjs/lib/getOffsetFromPage.js +8 -0
- package/dist/cjs/lib/getPageFromOffset.d.ts +3 -0
- package/dist/cjs/lib/getPageFromOffset.js +8 -0
- package/dist/cjs/lib/shallowEqual.d.ts +3 -0
- package/dist/cjs/lib/shallowEqual.js +24 -0
- package/dist/cjs/store/index.d.ts +14 -0
- package/dist/cjs/store/index.js +18 -0
- package/dist/cjs/store/localStorageStore.d.ts +5 -0
- package/dist/cjs/store/localStorageStore.js +33 -0
- package/dist/cjs/store/memoryStore.d.ts +5 -0
- package/dist/cjs/store/memoryStore.js +25 -0
- package/dist/cjs/store/reduxHelpers/createActions.d.ts +16 -0
- package/dist/cjs/store/reduxHelpers/createActions.js +16 -0
- package/dist/cjs/store/reduxHelpers/createReducer.d.ts +10 -0
- package/dist/cjs/store/reduxHelpers/createReducer.js +25 -0
- package/dist/cjs/store/reduxStore.d.ts +18 -0
- package/dist/cjs/store/reduxStore.js +63 -0
- package/dist/cjs/store/urlParamStore.d.ts +8 -0
- package/dist/cjs/store/urlParamStore.js +68 -0
- package/dist/cjs/useFilter.d.ts +106 -0
- package/dist/cjs/useFilter.js +251 -0
- package/dist/cjs/useSimpleFilter.d.ts +89 -0
- package/dist/cjs/useSimpleFilter.js +198 -0
- package/dist/esm/lib/buildDefaultFilterInfo.d.mts +6 -0
- package/dist/esm/lib/buildDefaultFilterInfo.mjs +27 -0
- package/dist/esm/lib/getOffsetFromPage.d.mts +4 -0
- package/dist/esm/lib/getOffsetFromPage.mjs +7 -0
- package/dist/esm/lib/getPageFromOffset.d.mts +4 -0
- package/dist/esm/lib/getPageFromOffset.mjs +7 -0
- package/dist/esm/lib/shallowEqual.d.mts +4 -0
- package/dist/esm/lib/shallowEqual.mjs +23 -0
- package/dist/esm/store/index.d.mts +14 -0
- package/dist/esm/store/index.mjs +15 -0
- package/dist/esm/store/localStorageStore.d.mts +6 -0
- package/dist/esm/store/localStorageStore.mjs +32 -0
- package/dist/esm/store/memoryStore.d.mts +6 -0
- package/dist/esm/store/memoryStore.mjs +24 -0
- package/dist/esm/store/reduxHelpers/createActions.d.mts +16 -0
- package/dist/esm/store/reduxHelpers/createActions.mjs +15 -0
- package/dist/esm/store/reduxHelpers/createReducer.d.mts +11 -0
- package/dist/esm/store/reduxHelpers/createReducer.mjs +24 -0
- package/dist/esm/store/reduxStore.d.mts +18 -0
- package/dist/esm/store/reduxStore.mjs +61 -0
- package/dist/esm/store/urlParamStore.d.mts +8 -0
- package/dist/esm/store/urlParamStore.mjs +63 -0
- package/dist/esm/useFilter.d.mts +106 -0
- package/dist/esm/useFilter.mjs +251 -0
- package/dist/esm/useSimpleFilter.d.mts +89 -0
- package/dist/esm/useSimpleFilter.mjs +198 -0
- package/package.json +1 -1
- package/.github/workflows/main.yml +0 -38
- package/.vscode/settings.json +0 -3
- package/CHANGELOG.md +0 -185
- package/Makefile +0 -25
- package/eslint.config.js +0 -30
- package/mise.toml +0 -3
- package/prettier.config.js +0 -3
- package/src/lib/buildDefaultFilterInfo.ts +0 -36
- package/src/lib/getOffsetFromPage.ts +0 -5
- package/src/lib/getPageFromOffset.ts +0 -5
- package/src/lib/shallowEqual.test.ts +0 -71
- package/src/lib/shallowEqual.ts +0 -26
- package/src/store/index.ts +0 -30
- package/src/store/localStorageStore.ts +0 -36
- package/src/store/memoryStore.ts +0 -27
- package/src/store/reduxHelpers/createActions.test.ts +0 -32
- package/src/store/reduxHelpers/createActions.ts +0 -56
- package/src/store/reduxHelpers/createReducer.test.ts +0 -65
- package/src/store/reduxHelpers/createReducer.ts +0 -47
- package/src/store/reduxStore.ts +0 -78
- package/src/store/urlParamStore.test.ts +0 -131
- package/src/store/urlParamStore.ts +0 -85
- package/src/useFilter.test.tsx +0 -822
- package/src/useFilter.ts +0 -524
- package/src/useSimpleFilter.test.tsx +0 -676
- package/src/useSimpleFilter.ts +0 -397
- package/src/vitest-env.d.ts +0 -1
- package/tsconfig.json +0 -76
- package/tsdown.config.ts +0 -30
- package/vite.config.ts +0 -9
package/src/useFilter.test.tsx
DELETED
|
@@ -1,822 +0,0 @@
|
|
|
1
|
-
import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
|
|
2
|
-
import { useState } from 'react'
|
|
3
|
-
|
|
4
|
-
import buildDefaultFilterInfo from './lib/buildDefaultFilterInfo'
|
|
5
|
-
import type { FilterStore } from './store'
|
|
6
|
-
import { setFilterStore } from './store'
|
|
7
|
-
import localStorageStore from './store/localStorageStore'
|
|
8
|
-
import memoryStore from './store/memoryStore'
|
|
9
|
-
import { createUrlParamStore } from './store/urlParamStore'
|
|
10
|
-
import type { FilterInfo } from './useFilter'
|
|
11
|
-
import useFilter from './useFilter'
|
|
12
|
-
|
|
13
|
-
async function runFilterTest(testOptions: { store: FilterStore }) {
|
|
14
|
-
describe('useFilter', () => {
|
|
15
|
-
const defaultFilterInfo = buildDefaultFilterInfo({
|
|
16
|
-
filter: {
|
|
17
|
-
foo: 'bar',
|
|
18
|
-
},
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('calls on mount', async () => {
|
|
22
|
-
const { onChangeMock } = await setup({ defaultFilterInfo })
|
|
23
|
-
|
|
24
|
-
expect(onChangeMock).toHaveBeenCalledTimes(1)
|
|
25
|
-
expect(onChangeMock).toHaveBeenLastCalledWith(
|
|
26
|
-
expect.objectContaining({ filter: { foo: 'bar' } }),
|
|
27
|
-
'initial',
|
|
28
|
-
)
|
|
29
|
-
expect(screen.getByTestId('updateReason').textContent).toBe('initial')
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('calls when data changes', async () => {
|
|
33
|
-
const { onChangeMock } = await setup({ defaultFilterInfo })
|
|
34
|
-
|
|
35
|
-
await actAndWait(() => {
|
|
36
|
-
fireEvent.click(screen.getByTestId('updateFilterButton'))
|
|
37
|
-
})
|
|
38
|
-
expect(onChangeMock).toHaveBeenCalledTimes(2)
|
|
39
|
-
expect(onChangeMock).toHaveBeenLastCalledWith(
|
|
40
|
-
expect.objectContaining({ filter: { foo: '42' } }),
|
|
41
|
-
'filter',
|
|
42
|
-
)
|
|
43
|
-
expect(screen.getByTestId('updateReason').textContent).toBe('filter')
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('uses shallowEqual be default', async () => {
|
|
47
|
-
const { onChangeMock } = await setup({ defaultFilterInfo })
|
|
48
|
-
|
|
49
|
-
await actAndWait(() => {
|
|
50
|
-
fireEvent.click(screen.getByTestId('updateFilterButton'))
|
|
51
|
-
})
|
|
52
|
-
await actAndWait(() => {
|
|
53
|
-
fireEvent.click(screen.getByTestId('updateFilterButton'))
|
|
54
|
-
})
|
|
55
|
-
// 2 is still the correct number of times, as there is one from the initial
|
|
56
|
-
// mount and the other from updateFilter.
|
|
57
|
-
expect(onChangeMock).toHaveBeenCalledTimes(2)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('can reset and keeps defaults', async () => {
|
|
61
|
-
const { onChangeMock } = await setup({ defaultFilterInfo })
|
|
62
|
-
|
|
63
|
-
await actAndWait(() => {
|
|
64
|
-
fireEvent.click(screen.getByTestId('resetFilterButton'))
|
|
65
|
-
})
|
|
66
|
-
expect(onChangeMock).toHaveBeenCalledTimes(2)
|
|
67
|
-
expect(onChangeMock).toHaveBeenLastCalledWith(
|
|
68
|
-
expect.objectContaining({ filter: { foo: 'bar' } }),
|
|
69
|
-
'filter',
|
|
70
|
-
)
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
it('can setSort', async () => {
|
|
74
|
-
const { onChangeMock } = await setup({ defaultFilterInfo })
|
|
75
|
-
|
|
76
|
-
await actAndWait(() => {
|
|
77
|
-
fireEvent.click(screen.getByTestId('setSortButton'))
|
|
78
|
-
})
|
|
79
|
-
expect(onChangeMock).toHaveBeenCalledTimes(2)
|
|
80
|
-
expect(onChangeMock).toHaveBeenLastCalledWith(
|
|
81
|
-
expect.objectContaining({ filter: { foo: 'bar' }, sort: 'foo:bar' }),
|
|
82
|
-
'filter',
|
|
83
|
-
)
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
it('renders the current filter', async () => {
|
|
87
|
-
await setup({ defaultFilterInfo })
|
|
88
|
-
|
|
89
|
-
expect(
|
|
90
|
-
safeJsonParse(screen.getByTestId('filterJson').textContent),
|
|
91
|
-
).toEqual(expect.objectContaining({ filter: { foo: 'bar' } }))
|
|
92
|
-
|
|
93
|
-
await actAndWait(() => {
|
|
94
|
-
fireEvent.click(screen.getByTestId('updateFilterButton'))
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
expect(
|
|
98
|
-
safeJsonParse(screen.getByTestId('filterJson').textContent),
|
|
99
|
-
).toEqual(expect.objectContaining({ filter: { foo: '42' } }))
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('handles onChange.filterInfo', async () => {
|
|
103
|
-
const { onChangeMock } = await setup({ defaultFilterInfo })
|
|
104
|
-
|
|
105
|
-
expect(
|
|
106
|
-
safeJsonParse(screen.getByTestId('filterJson').textContent),
|
|
107
|
-
).toEqual(expect.objectContaining({ totalResults: 1 }))
|
|
108
|
-
|
|
109
|
-
// Say the server response with a new totalResults.
|
|
110
|
-
onChangeMock.mockImplementation(() => ({
|
|
111
|
-
filterInfo: {
|
|
112
|
-
totalResults: 100,
|
|
113
|
-
},
|
|
114
|
-
}))
|
|
115
|
-
await actAndWait(() => {
|
|
116
|
-
fireEvent.click(screen.getByTestId('forceRefreshButton'))
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
expect(
|
|
120
|
-
safeJsonParse(screen.getByTestId('filterJson').textContent),
|
|
121
|
-
).toEqual(expect.objectContaining({ totalResults: 100 }))
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
it('handles onChange.filterInfo.totalResults', async () => {
|
|
125
|
-
const { onChangeMock } = await setup({ defaultFilterInfo })
|
|
126
|
-
|
|
127
|
-
// Say the server response with a new totalResults.
|
|
128
|
-
onChangeMock.mockImplementation(() => ({
|
|
129
|
-
filterInfo: {
|
|
130
|
-
totalResults: 99,
|
|
131
|
-
},
|
|
132
|
-
}))
|
|
133
|
-
await actAndWait(() => {
|
|
134
|
-
fireEvent.click(screen.getByTestId('forceRefreshButton'))
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
expect(
|
|
138
|
-
safeJsonParse(screen.getByTestId('filterJson').textContent),
|
|
139
|
-
).toEqual(expect.objectContaining({ totalResults: 99 }))
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
it('handles onChange.data', async () => {
|
|
143
|
-
const { onChangeMock } = await setup({ defaultFilterInfo })
|
|
144
|
-
|
|
145
|
-
onChangeMock.mockImplementation((filterInfo) => ({
|
|
146
|
-
data: [{ name: filterInfo.filter.foo }],
|
|
147
|
-
}))
|
|
148
|
-
|
|
149
|
-
// Need to trigger an update so that our onChangeMock implementation takes
|
|
150
|
-
// effect.
|
|
151
|
-
await actAndWait(() => {
|
|
152
|
-
fireEvent.click(screen.getByTestId('updateFilterButton'))
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
expect(
|
|
156
|
-
safeJsonParse(screen.getByTestId('responseDataJson').textContent),
|
|
157
|
-
).toEqual([{ name: '42' }])
|
|
158
|
-
|
|
159
|
-
await actAndWait(() => {
|
|
160
|
-
fireEvent.click(screen.getByTestId('resetFilterButton'))
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
expect(
|
|
164
|
-
safeJsonParse(screen.getByTestId('responseDataJson').textContent),
|
|
165
|
-
).toEqual([{ name: 'bar' }])
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
it('handles async onChange.filterInfo', async () => {
|
|
169
|
-
const { onChangeMock } = await setup({ defaultFilterInfo })
|
|
170
|
-
|
|
171
|
-
expect(
|
|
172
|
-
safeJsonParse(screen.getByTestId('filterJson').textContent),
|
|
173
|
-
).toEqual(expect.objectContaining({ totalResults: 1 }))
|
|
174
|
-
|
|
175
|
-
// Say the server response with a new totalResults.
|
|
176
|
-
onChangeMock.mockImplementation(() =>
|
|
177
|
-
Promise.resolve({
|
|
178
|
-
filterInfo: {
|
|
179
|
-
totalResults: 100,
|
|
180
|
-
},
|
|
181
|
-
}),
|
|
182
|
-
)
|
|
183
|
-
await actAndWait(() => {
|
|
184
|
-
fireEvent.click(screen.getByTestId('forceRefreshButton'))
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
expect(
|
|
188
|
-
safeJsonParse(screen.getByTestId('filterJson').textContent),
|
|
189
|
-
).toEqual(expect.objectContaining({ totalResults: 100 }))
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
it('handles async onChange.filterInfo.totalResults', async () => {
|
|
193
|
-
const { onChangeMock } = await setup({ defaultFilterInfo })
|
|
194
|
-
|
|
195
|
-
// Say the server response with a new totalResults.
|
|
196
|
-
onChangeMock.mockImplementation(() =>
|
|
197
|
-
Promise.resolve({
|
|
198
|
-
filterInfo: {
|
|
199
|
-
totalResults: 99,
|
|
200
|
-
},
|
|
201
|
-
}),
|
|
202
|
-
)
|
|
203
|
-
await actAndWait(() => {
|
|
204
|
-
fireEvent.click(screen.getByTestId('forceRefreshButton'))
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
expect(
|
|
208
|
-
safeJsonParse(screen.getByTestId('filterJson').textContent),
|
|
209
|
-
).toEqual(expect.objectContaining({ totalPages: 5 }))
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
it('handles async onChange.data', async () => {
|
|
213
|
-
const { onChangeMock } = await setup({ defaultFilterInfo })
|
|
214
|
-
|
|
215
|
-
onChangeMock.mockImplementation((filterInfo) =>
|
|
216
|
-
Promise.resolve({
|
|
217
|
-
data: [{ name: filterInfo.filter.foo }],
|
|
218
|
-
}),
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
await actAndWait(() => {
|
|
222
|
-
fireEvent.click(screen.getByTestId('updateFilterButton'))
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
expect(
|
|
226
|
-
safeJsonParse(screen.getByTestId('responseDataJson').textContent),
|
|
227
|
-
).toEqual([{ name: '42' }])
|
|
228
|
-
|
|
229
|
-
await actAndWait(() => {
|
|
230
|
-
fireEvent.click(screen.getByTestId('resetFilterButton'))
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
expect(
|
|
234
|
-
safeJsonParse(screen.getByTestId('responseDataJson').textContent),
|
|
235
|
-
).toEqual([{ name: 'bar' }])
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
it('persists across mounts', async () => {
|
|
239
|
-
const { onChangeMock, ExampleComponent } = await setup({
|
|
240
|
-
defaultFilterInfo,
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
const ToggleComponent = () => {
|
|
244
|
-
const [shouldShow, setShouldShow] = useState(true)
|
|
245
|
-
|
|
246
|
-
return (
|
|
247
|
-
<div>
|
|
248
|
-
<button
|
|
249
|
-
data-testid="toggleShouldShow"
|
|
250
|
-
onClick={() => setShouldShow(!shouldShow)}
|
|
251
|
-
/>
|
|
252
|
-
{shouldShow ? <ExampleComponent /> : null}
|
|
253
|
-
</div>
|
|
254
|
-
)
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Simulate the filter being visible.
|
|
258
|
-
await actAndWait(() => {
|
|
259
|
-
// Cleanup the render done in `setup`.
|
|
260
|
-
cleanup()
|
|
261
|
-
render(<ToggleComponent />)
|
|
262
|
-
})
|
|
263
|
-
expect(screen.queryByTestId('updateFilterButton')).toBeTruthy()
|
|
264
|
-
|
|
265
|
-
// Simulate removing the filter.
|
|
266
|
-
await actAndWait(() => {
|
|
267
|
-
fireEvent.click(screen.getByTestId('toggleShouldShow'))
|
|
268
|
-
})
|
|
269
|
-
expect(screen.queryByTestId('updateFilterButton')).toBeFalsy()
|
|
270
|
-
|
|
271
|
-
// Simulate the filter being visible, again.
|
|
272
|
-
await actAndWait(() => {
|
|
273
|
-
fireEvent.click(screen.getByTestId('toggleShouldShow'))
|
|
274
|
-
})
|
|
275
|
-
expect(screen.queryByTestId('updateFilterButton')).toBeTruthy()
|
|
276
|
-
|
|
277
|
-
// onChange should still only be called once.
|
|
278
|
-
expect(onChangeMock).toHaveBeenCalledTimes(1)
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
it('uses onBeforeSaveFilter to control what is saved', async () => {
|
|
282
|
-
const { ExampleComponent } = await setup({
|
|
283
|
-
defaultFilterInfo,
|
|
284
|
-
onBeforeSaveFilter: (filterInfo) => {
|
|
285
|
-
return {
|
|
286
|
-
...filterInfo,
|
|
287
|
-
filter: {
|
|
288
|
-
...filterInfo.filter,
|
|
289
|
-
somethingAddedInOnBeforeSaveFilter: 'hello world',
|
|
290
|
-
},
|
|
291
|
-
}
|
|
292
|
-
},
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
const ToggleComponent = () => {
|
|
296
|
-
const [shouldShow, setShouldShow] = useState(true)
|
|
297
|
-
|
|
298
|
-
return (
|
|
299
|
-
<div>
|
|
300
|
-
<button
|
|
301
|
-
data-testid="toggleShouldShow"
|
|
302
|
-
onClick={() => setShouldShow(!shouldShow)}
|
|
303
|
-
/>
|
|
304
|
-
{shouldShow ? <ExampleComponent /> : null}
|
|
305
|
-
</div>
|
|
306
|
-
)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Simulate the filter being visible.
|
|
310
|
-
await actAndWait(() => {
|
|
311
|
-
// Cleanup the render done in `setup`.
|
|
312
|
-
cleanup()
|
|
313
|
-
render(<ToggleComponent />)
|
|
314
|
-
})
|
|
315
|
-
expect(screen.queryByTestId('updateFilterButton')).toBeTruthy()
|
|
316
|
-
|
|
317
|
-
// Do something to ensure filter is saved.
|
|
318
|
-
await actAndWait(() => {
|
|
319
|
-
fireEvent.click(screen.getByTestId('updateFilterButton'))
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
// Simulate removing the filter.
|
|
323
|
-
await actAndWait(() => {
|
|
324
|
-
fireEvent.click(screen.getByTestId('toggleShouldShow'))
|
|
325
|
-
})
|
|
326
|
-
expect(screen.queryByTestId('updateFilterButton')).toBeFalsy()
|
|
327
|
-
|
|
328
|
-
// Simulate the filter being visible, again.
|
|
329
|
-
await actAndWait(() => {
|
|
330
|
-
fireEvent.click(screen.getByTestId('toggleShouldShow'))
|
|
331
|
-
})
|
|
332
|
-
expect(screen.queryByTestId('updateFilterButton')).toBeTruthy()
|
|
333
|
-
|
|
334
|
-
// Verify that the data we added in `onBeforeSaveFilter` is there.
|
|
335
|
-
expect(
|
|
336
|
-
safeJsonParse(screen.getByTestId('filterJson').textContent),
|
|
337
|
-
).toEqual(
|
|
338
|
-
expect.objectContaining({
|
|
339
|
-
filter: expect.objectContaining({
|
|
340
|
-
somethingAddedInOnBeforeSaveFilter: 'hello world',
|
|
341
|
-
}),
|
|
342
|
-
}),
|
|
343
|
-
)
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
it('can setPageSize', async () => {
|
|
347
|
-
const defaultFilterInfo = buildDefaultFilterInfo({
|
|
348
|
-
filter: {
|
|
349
|
-
foo: 'bar',
|
|
350
|
-
},
|
|
351
|
-
pageSize: 20,
|
|
352
|
-
})
|
|
353
|
-
|
|
354
|
-
const children: TestCaseChildren = (api) => (
|
|
355
|
-
<>
|
|
356
|
-
<button
|
|
357
|
-
data-testid="setPageButton"
|
|
358
|
-
onClick={() => api.setPage(api.filterInfo.page + 1)}
|
|
359
|
-
/>
|
|
360
|
-
<button
|
|
361
|
-
data-testid="setPageSizeButton"
|
|
362
|
-
onClick={() => api.setPageSize(5)}
|
|
363
|
-
/>
|
|
364
|
-
</>
|
|
365
|
-
)
|
|
366
|
-
|
|
367
|
-
const { onChangeMock } = await setup({ defaultFilterInfo, children })
|
|
368
|
-
|
|
369
|
-
await actAndWait(() => {
|
|
370
|
-
fireEvent.click(screen.getByTestId('setPageButton'))
|
|
371
|
-
})
|
|
372
|
-
expect(onChangeMock).toBeCalledTimes(2)
|
|
373
|
-
expect(onChangeMock).toHaveBeenLastCalledWith(
|
|
374
|
-
expect.objectContaining({
|
|
375
|
-
page: 2,
|
|
376
|
-
offset: 20,
|
|
377
|
-
}),
|
|
378
|
-
'pagination',
|
|
379
|
-
)
|
|
380
|
-
|
|
381
|
-
await actAndWait(() => {
|
|
382
|
-
fireEvent.click(screen.getByTestId('setPageSizeButton'))
|
|
383
|
-
})
|
|
384
|
-
expect(onChangeMock).toBeCalledTimes(3)
|
|
385
|
-
expect(onChangeMock).toHaveBeenLastCalledWith(
|
|
386
|
-
expect.objectContaining({
|
|
387
|
-
page: 5,
|
|
388
|
-
offset: 20,
|
|
389
|
-
}),
|
|
390
|
-
'pagination',
|
|
391
|
-
)
|
|
392
|
-
})
|
|
393
|
-
|
|
394
|
-
it('lets you pass your own store', async () => {
|
|
395
|
-
const mockStore: FilterStore = {
|
|
396
|
-
getFilter: vitest.fn(),
|
|
397
|
-
saveFilter: vitest.fn(),
|
|
398
|
-
getData: vitest.fn(),
|
|
399
|
-
saveData: vitest.fn(),
|
|
400
|
-
clear: vitest.fn(),
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
await setup({
|
|
404
|
-
defaultFilterInfo,
|
|
405
|
-
store: mockStore,
|
|
406
|
-
})
|
|
407
|
-
|
|
408
|
-
expect(mockStore.getFilter).toHaveBeenCalled()
|
|
409
|
-
expect(mockStore.saveFilter).toHaveBeenCalled()
|
|
410
|
-
})
|
|
411
|
-
|
|
412
|
-
it('handles stores not returning a full filterInfo', async () => {
|
|
413
|
-
const mockStore: FilterStore = {
|
|
414
|
-
getFilter: vitest
|
|
415
|
-
.fn()
|
|
416
|
-
.mockReturnValue({ filter: { fromStore: 'lol' } }),
|
|
417
|
-
saveFilter: vitest.fn(),
|
|
418
|
-
getData: vitest.fn(),
|
|
419
|
-
saveData: vitest.fn(),
|
|
420
|
-
clear: vitest.fn(),
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
await setup({
|
|
424
|
-
defaultFilterInfo,
|
|
425
|
-
store: mockStore,
|
|
426
|
-
})
|
|
427
|
-
|
|
428
|
-
expect(
|
|
429
|
-
safeJsonParse(screen.getByTestId('filterJson').textContent),
|
|
430
|
-
).toEqual(
|
|
431
|
-
expect.objectContaining({
|
|
432
|
-
// This asserts that when a store returns a `Partial<FilterInfo>` it's
|
|
433
|
-
// turned into a full `FilterInfo`.
|
|
434
|
-
page: 1,
|
|
435
|
-
}),
|
|
436
|
-
)
|
|
437
|
-
})
|
|
438
|
-
|
|
439
|
-
it('merges stored filters with whats passed to the hook', async () => {
|
|
440
|
-
const mockStore: FilterStore = {
|
|
441
|
-
getFilter: vitest
|
|
442
|
-
.fn()
|
|
443
|
-
.mockReturnValue({ filter: { fromStore: 'lol' } }),
|
|
444
|
-
saveFilter: vitest.fn(),
|
|
445
|
-
getData: vitest.fn(),
|
|
446
|
-
saveData: vitest.fn(),
|
|
447
|
-
clear: vitest.fn(),
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
await setup({
|
|
451
|
-
defaultFilterInfo,
|
|
452
|
-
store: mockStore,
|
|
453
|
-
})
|
|
454
|
-
|
|
455
|
-
expect(
|
|
456
|
-
safeJsonParse(screen.getByTestId('filterJson').textContent),
|
|
457
|
-
).toEqual(
|
|
458
|
-
expect.objectContaining({
|
|
459
|
-
filter: {
|
|
460
|
-
// This asserts that whatever the store returns is included.
|
|
461
|
-
fromStore: 'lol',
|
|
462
|
-
// This asserts that whatever defaultFilterInfo passed to the hook is
|
|
463
|
-
// included.
|
|
464
|
-
foo: 'bar',
|
|
465
|
-
},
|
|
466
|
-
}),
|
|
467
|
-
)
|
|
468
|
-
})
|
|
469
|
-
})
|
|
470
|
-
|
|
471
|
-
describe('page based', () => {
|
|
472
|
-
const defaultFilterInfo = buildDefaultFilterInfo({
|
|
473
|
-
filter: {
|
|
474
|
-
foo: 'bar',
|
|
475
|
-
},
|
|
476
|
-
pageSize: 20,
|
|
477
|
-
page: 2,
|
|
478
|
-
})
|
|
479
|
-
|
|
480
|
-
const children: TestCaseChildren = (api) => (
|
|
481
|
-
<button data-testid="setPageButton" onClick={() => api.setPage(3)} />
|
|
482
|
-
)
|
|
483
|
-
|
|
484
|
-
it('accepts initial page', async () => {
|
|
485
|
-
await setup({ defaultFilterInfo, children })
|
|
486
|
-
|
|
487
|
-
expect(
|
|
488
|
-
safeJsonParse(screen.getByTestId('filterJson').textContent),
|
|
489
|
-
).toEqual(expect.objectContaining({ page: 2, offset: 20 }))
|
|
490
|
-
})
|
|
491
|
-
|
|
492
|
-
it('can set page', async () => {
|
|
493
|
-
const { onChangeMock } = await setup({ defaultFilterInfo, children })
|
|
494
|
-
|
|
495
|
-
await actAndWait(() => {
|
|
496
|
-
fireEvent.click(screen.getByTestId('setPageButton'))
|
|
497
|
-
})
|
|
498
|
-
|
|
499
|
-
expect(onChangeMock).toHaveBeenCalledTimes(2)
|
|
500
|
-
expect(onChangeMock).toHaveBeenLastCalledWith(
|
|
501
|
-
expect.objectContaining({ page: 3, offset: 40 }),
|
|
502
|
-
'pagination',
|
|
503
|
-
)
|
|
504
|
-
expect(screen.getByTestId('updateReason').textContent).toBe('pagination')
|
|
505
|
-
})
|
|
506
|
-
|
|
507
|
-
it('resets pagination when filters change', async () => {
|
|
508
|
-
const { onChangeMock } = await setup({ defaultFilterInfo, children })
|
|
509
|
-
|
|
510
|
-
await actAndWait(() => {
|
|
511
|
-
fireEvent.click(screen.getByTestId('setPageButton'))
|
|
512
|
-
})
|
|
513
|
-
expect(onChangeMock).toHaveBeenCalledTimes(2)
|
|
514
|
-
expect(onChangeMock).toHaveBeenLastCalledWith(
|
|
515
|
-
expect.objectContaining({ page: 3, offset: 40 }),
|
|
516
|
-
'pagination',
|
|
517
|
-
)
|
|
518
|
-
|
|
519
|
-
await actAndWait(() => {
|
|
520
|
-
fireEvent.click(screen.getByTestId('updateFilterButton'))
|
|
521
|
-
})
|
|
522
|
-
expect(onChangeMock).toHaveBeenCalledTimes(3)
|
|
523
|
-
expect(onChangeMock).toHaveBeenLastCalledWith(
|
|
524
|
-
expect.objectContaining({ page: 1 }),
|
|
525
|
-
'filter',
|
|
526
|
-
)
|
|
527
|
-
|
|
528
|
-
expect(screen.getByTestId('updateReason').textContent).toBe('filter')
|
|
529
|
-
})
|
|
530
|
-
|
|
531
|
-
it('can handle onChange.filterInfo.page', async () => {
|
|
532
|
-
const { onChangeMock } = await setup({ defaultFilterInfo, children })
|
|
533
|
-
|
|
534
|
-
// Intentionally respond with numbers that don't match what the button
|
|
535
|
-
// actually does.
|
|
536
|
-
// This is to test the actual response, the view updates immediately.
|
|
537
|
-
onChangeMock.mockImplementation(() => ({
|
|
538
|
-
filterInfo: {
|
|
539
|
-
page: 20,
|
|
540
|
-
},
|
|
541
|
-
}))
|
|
542
|
-
|
|
543
|
-
await actAndWait(() => {
|
|
544
|
-
fireEvent.click(screen.getByTestId('setPageButton'))
|
|
545
|
-
})
|
|
546
|
-
|
|
547
|
-
expect(
|
|
548
|
-
safeJsonParse(screen.getByTestId('filterJson').textContent),
|
|
549
|
-
).toEqual(expect.objectContaining({ page: 20, offset: 380 }))
|
|
550
|
-
})
|
|
551
|
-
})
|
|
552
|
-
|
|
553
|
-
describe('offset based', () => {
|
|
554
|
-
const defaultFilterInfo = buildDefaultFilterInfo({
|
|
555
|
-
filter: {
|
|
556
|
-
foo: 'bar',
|
|
557
|
-
},
|
|
558
|
-
pageSize: 20,
|
|
559
|
-
offset: 80,
|
|
560
|
-
})
|
|
561
|
-
|
|
562
|
-
const children: TestCaseChildren = (api) => (
|
|
563
|
-
<button
|
|
564
|
-
data-testid="setOffsetButton"
|
|
565
|
-
onClick={() => api.setOffset(100)}
|
|
566
|
-
/>
|
|
567
|
-
)
|
|
568
|
-
|
|
569
|
-
it('accepts initial offset', async () => {
|
|
570
|
-
await setup({ defaultFilterInfo, children })
|
|
571
|
-
|
|
572
|
-
expect(
|
|
573
|
-
safeJsonParse(screen.getByTestId('filterJson').textContent),
|
|
574
|
-
).toEqual(expect.objectContaining({ offset: 80, page: 5 }))
|
|
575
|
-
})
|
|
576
|
-
|
|
577
|
-
it('can set offset', async () => {
|
|
578
|
-
const { onChangeMock } = await setup({ defaultFilterInfo, children })
|
|
579
|
-
|
|
580
|
-
await actAndWait(() => {
|
|
581
|
-
fireEvent.click(screen.getByTestId('setOffsetButton'))
|
|
582
|
-
})
|
|
583
|
-
|
|
584
|
-
expect(onChangeMock).toHaveBeenCalledTimes(2)
|
|
585
|
-
expect(onChangeMock).toHaveBeenLastCalledWith(
|
|
586
|
-
expect.objectContaining({ page: 6, offset: 100 }),
|
|
587
|
-
'pagination',
|
|
588
|
-
)
|
|
589
|
-
expect(screen.getByTestId('updateReason').textContent).toBe('pagination')
|
|
590
|
-
})
|
|
591
|
-
|
|
592
|
-
it('resets pagination when filters change', async () => {
|
|
593
|
-
const { onChangeMock } = await setup({ defaultFilterInfo, children })
|
|
594
|
-
|
|
595
|
-
await actAndWait(() => {
|
|
596
|
-
fireEvent.click(screen.getByTestId('setOffsetButton'))
|
|
597
|
-
})
|
|
598
|
-
expect(onChangeMock).toHaveBeenCalledTimes(2)
|
|
599
|
-
expect(onChangeMock).toHaveBeenLastCalledWith(
|
|
600
|
-
expect.objectContaining({ page: 6, offset: 100 }),
|
|
601
|
-
'pagination',
|
|
602
|
-
)
|
|
603
|
-
|
|
604
|
-
await actAndWait(() => {
|
|
605
|
-
fireEvent.click(screen.getByTestId('updateFilterButton'))
|
|
606
|
-
})
|
|
607
|
-
expect(onChangeMock).toHaveBeenCalledTimes(3)
|
|
608
|
-
expect(onChangeMock).toHaveBeenLastCalledWith(
|
|
609
|
-
expect.objectContaining({ offset: 0 }),
|
|
610
|
-
'filter',
|
|
611
|
-
)
|
|
612
|
-
|
|
613
|
-
expect(screen.getByTestId('updateReason').textContent).toBe('filter')
|
|
614
|
-
})
|
|
615
|
-
|
|
616
|
-
it('can handle onChange.filterInfo.offset', async () => {
|
|
617
|
-
const { onChangeMock } = await setup({ defaultFilterInfo, children })
|
|
618
|
-
|
|
619
|
-
// Intentionally respond with numbers that don't match what the button
|
|
620
|
-
// actually does.
|
|
621
|
-
// This is to test the actual response, the view updates immediately.
|
|
622
|
-
onChangeMock.mockImplementation(() => ({
|
|
623
|
-
filterInfo: {
|
|
624
|
-
offset: 380,
|
|
625
|
-
},
|
|
626
|
-
}))
|
|
627
|
-
await actAndWait(() => {
|
|
628
|
-
fireEvent.click(screen.getByTestId('setOffsetButton'))
|
|
629
|
-
})
|
|
630
|
-
|
|
631
|
-
expect(
|
|
632
|
-
safeJsonParse(screen.getByTestId('filterJson').textContent),
|
|
633
|
-
).toEqual(expect.objectContaining({ page: 20, offset: 380 }))
|
|
634
|
-
})
|
|
635
|
-
})
|
|
636
|
-
|
|
637
|
-
describe('cursor based', () => {
|
|
638
|
-
const defaultFilterInfo = buildDefaultFilterInfo({
|
|
639
|
-
filter: {
|
|
640
|
-
foo: 'bar',
|
|
641
|
-
},
|
|
642
|
-
cursor: 'currentCursor',
|
|
643
|
-
nextCursor: 'nextCursor',
|
|
644
|
-
})
|
|
645
|
-
|
|
646
|
-
const children: TestCaseChildren = (api) => (
|
|
647
|
-
<button
|
|
648
|
-
data-testid="setCursorButton"
|
|
649
|
-
onClick={() => api.setCursor(api.filterInfo.nextCursor)}
|
|
650
|
-
/>
|
|
651
|
-
)
|
|
652
|
-
|
|
653
|
-
it('accepts initial cursor', async () => {
|
|
654
|
-
await setup({ defaultFilterInfo, children })
|
|
655
|
-
|
|
656
|
-
expect(
|
|
657
|
-
safeJsonParse(screen.getByTestId('filterJson').textContent),
|
|
658
|
-
).toEqual(expect.objectContaining({ cursor: 'currentCursor' }))
|
|
659
|
-
})
|
|
660
|
-
|
|
661
|
-
it('can set cursor', async () => {
|
|
662
|
-
const { onChangeMock } = await setup({ defaultFilterInfo, children })
|
|
663
|
-
|
|
664
|
-
await actAndWait(() => {
|
|
665
|
-
fireEvent.click(screen.getByTestId('setCursorButton'))
|
|
666
|
-
})
|
|
667
|
-
|
|
668
|
-
expect(onChangeMock).toHaveBeenCalledTimes(2)
|
|
669
|
-
expect(onChangeMock).toHaveBeenLastCalledWith(
|
|
670
|
-
expect.objectContaining({ cursor: 'nextCursor' }),
|
|
671
|
-
'pagination',
|
|
672
|
-
)
|
|
673
|
-
expect(screen.getByTestId('updateReason').textContent).toBe('pagination')
|
|
674
|
-
})
|
|
675
|
-
|
|
676
|
-
it('resets cursor when filters change', async () => {
|
|
677
|
-
const { onChangeMock } = await setup({ defaultFilterInfo, children })
|
|
678
|
-
|
|
679
|
-
await actAndWait(() => {
|
|
680
|
-
fireEvent.click(screen.getByTestId('setCursorButton'))
|
|
681
|
-
})
|
|
682
|
-
expect(onChangeMock).toHaveBeenCalledTimes(2)
|
|
683
|
-
expect(onChangeMock).toHaveBeenLastCalledWith(
|
|
684
|
-
expect.objectContaining({ cursor: 'nextCursor' }),
|
|
685
|
-
'pagination',
|
|
686
|
-
)
|
|
687
|
-
|
|
688
|
-
await actAndWait(() => {
|
|
689
|
-
fireEvent.click(screen.getByTestId('updateFilterButton'))
|
|
690
|
-
})
|
|
691
|
-
expect(onChangeMock).toHaveBeenCalledTimes(3)
|
|
692
|
-
expect(onChangeMock).toHaveBeenLastCalledWith(
|
|
693
|
-
expect.objectContaining({ cursor: undefined }),
|
|
694
|
-
'filter',
|
|
695
|
-
)
|
|
696
|
-
|
|
697
|
-
expect(screen.getByTestId('updateReason').textContent).toBe('filter')
|
|
698
|
-
})
|
|
699
|
-
|
|
700
|
-
it('can handle onChange.filterInfo.nextCursor', async () => {
|
|
701
|
-
const { onChangeMock } = await setup({ defaultFilterInfo, children })
|
|
702
|
-
|
|
703
|
-
onChangeMock.mockImplementation(() => ({
|
|
704
|
-
filterInfo: {
|
|
705
|
-
nextCursor: 'nextCursorFromResponse',
|
|
706
|
-
},
|
|
707
|
-
}))
|
|
708
|
-
await actAndWait(() => {
|
|
709
|
-
fireEvent.click(screen.getByTestId('setCursorButton'))
|
|
710
|
-
})
|
|
711
|
-
|
|
712
|
-
expect(
|
|
713
|
-
safeJsonParse(screen.getByTestId('filterJson').textContent),
|
|
714
|
-
).toEqual(
|
|
715
|
-
expect.objectContaining({ nextCursor: 'nextCursorFromResponse' }),
|
|
716
|
-
)
|
|
717
|
-
})
|
|
718
|
-
})
|
|
719
|
-
|
|
720
|
-
function wait(n = 0) {
|
|
721
|
-
return new Promise((resolve) => setTimeout(resolve, n))
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
function safeJsonParse(input: string | undefined | null = '') {
|
|
725
|
-
if (input) {
|
|
726
|
-
return JSON.parse(input)
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
async function actAndWait(fn: () => void, n = 0) {
|
|
731
|
-
act(fn)
|
|
732
|
-
await act(async () => {
|
|
733
|
-
await wait(n)
|
|
734
|
-
})
|
|
735
|
-
// You'd think you could just write this:
|
|
736
|
-
// await act(async () => {
|
|
737
|
-
// fn();
|
|
738
|
-
// await wait(n);
|
|
739
|
-
// });
|
|
740
|
-
// But for some reason, doing it that way needs a longer wait duration than
|
|
741
|
-
// just a single tick. /shrug.
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
type TestCaseChildren = (api: ReturnType<typeof useFilter>) => React.ReactNode
|
|
745
|
-
|
|
746
|
-
async function setup<TFilterInfo extends FilterInfo<any>>(options: {
|
|
747
|
-
defaultFilterInfo: TFilterInfo
|
|
748
|
-
children?: TestCaseChildren
|
|
749
|
-
onBeforeSaveFilter?: (filterInfo: TFilterInfo) => TFilterInfo
|
|
750
|
-
store?: FilterStore
|
|
751
|
-
}) {
|
|
752
|
-
testOptions.store.clear()
|
|
753
|
-
setFilterStore(testOptions.store)
|
|
754
|
-
|
|
755
|
-
const onChangeMock = vitest.fn()
|
|
756
|
-
|
|
757
|
-
const ExampleComponent = () => {
|
|
758
|
-
const api = useFilter('namespace', {
|
|
759
|
-
defaultFilterInfo: options.defaultFilterInfo,
|
|
760
|
-
debounceDuration: 0,
|
|
761
|
-
onChange: onChangeMock,
|
|
762
|
-
store: options.store,
|
|
763
|
-
// This is a very annoying warning in TypeScript:
|
|
764
|
-
//
|
|
765
|
-
// 'FilterInfo<any>' is assignable to the constraint of type
|
|
766
|
-
// 'TFilterInfo', but 'TFilterInfo' could be instantiated with a
|
|
767
|
-
// different subtype of constraint 'FilterInfo<any>'
|
|
768
|
-
//
|
|
769
|
-
// Which I don't think is valid, because you cannot do what's is saying is
|
|
770
|
-
// possible, as this errors (as expected):
|
|
771
|
-
//
|
|
772
|
-
// setup({
|
|
773
|
-
// defaultFilterInfo: {
|
|
774
|
-
// lol: '24',
|
|
775
|
-
// }
|
|
776
|
-
// })
|
|
777
|
-
//
|
|
778
|
-
// So I don't know why TS thinks it can be something else, it's already
|
|
779
|
-
// preventing you from doing that.
|
|
780
|
-
onBeforeSaveFilter: options.onBeforeSaveFilter as unknown as any,
|
|
781
|
-
})
|
|
782
|
-
|
|
783
|
-
return (
|
|
784
|
-
<div>
|
|
785
|
-
{options.children?.(api)}
|
|
786
|
-
<button
|
|
787
|
-
data-testid="updateFilterButton"
|
|
788
|
-
onClick={() => api.updateFilter({ foo: '42' })}
|
|
789
|
-
/>
|
|
790
|
-
<button
|
|
791
|
-
data-testid="resetFilterButton"
|
|
792
|
-
onClick={() => api.resetFilter()}
|
|
793
|
-
/>
|
|
794
|
-
<button
|
|
795
|
-
data-testid="forceRefreshButton"
|
|
796
|
-
onClick={() => api.forceRefresh()}
|
|
797
|
-
/>
|
|
798
|
-
<button
|
|
799
|
-
data-testid="setSortButton"
|
|
800
|
-
onClick={() => api.setSort('foo:bar')}
|
|
801
|
-
/>
|
|
802
|
-
<div data-testid="filterJson">{JSON.stringify(api.filterInfo)}</div>
|
|
803
|
-
<div data-testid="responseDataJson">{JSON.stringify(api.data)}</div>
|
|
804
|
-
<div data-testid="updateReason">{api.updateReason}</div>
|
|
805
|
-
</div>
|
|
806
|
-
)
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
await actAndWait(() => {
|
|
810
|
-
render(<ExampleComponent />)
|
|
811
|
-
})
|
|
812
|
-
|
|
813
|
-
return {
|
|
814
|
-
onChangeMock,
|
|
815
|
-
ExampleComponent,
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
runFilterTest({ store: memoryStore })
|
|
821
|
-
runFilterTest({ store: createUrlParamStore() })
|
|
822
|
-
runFilterTest({ store: localStorageStore })
|