11import { describe , expect , it } from 'vitest'
2+ import { mountSuspended } from '@nuxt/test-utils/runtime'
23import type { NpmSearchResult } from '#shared/types'
34
45// Helper to create mock package results
@@ -29,40 +30,60 @@ function createPackage(overrides: {
2930 }
3031}
3132
33+ /**
34+ * Helper to test useStructuredFilters by wrapping it in a component.
35+ * This is required because the composable uses useI18n which must be
36+ * called inside a Vue component's setup function.
37+ */
38+ async function useStructuredFiltersInComponent ( packages : Ref < NpmSearchResult [ ] > ) {
39+ let capturedResult : ReturnType < typeof useStructuredFilters >
40+
41+ const WrapperComponent = defineComponent ( {
42+ setup ( ) {
43+ capturedResult = useStructuredFilters ( { packages } )
44+ return ( ) => h ( 'div' )
45+ } ,
46+ } )
47+
48+ await mountSuspended ( WrapperComponent )
49+
50+ return capturedResult !
51+ }
52+
3253describe ( 'useStructuredFilters' , ( ) => {
3354 describe ( 'keyword filtering (AND logic)' , ( ) => {
34- it ( 'returns all packages when no keywords selected' , ( ) => {
55+ it ( 'returns all packages when no keywords selected' , async ( ) => {
3556 const packages = ref ( [
3657 createPackage ( { name : 'pkg-a' , keywords : [ 'react' ] } ) ,
3758 createPackage ( { name : 'pkg-b' , keywords : [ 'vue' ] } ) ,
3859 ] )
3960
40- const { sortedPackages } = useStructuredFilters ( { packages } )
61+ const { sortedPackages } = await useStructuredFiltersInComponent ( packages )
4162
4263 expect ( sortedPackages . value ) . toHaveLength ( 2 )
4364 } )
4465
45- it ( 'filters to packages with single keyword' , ( ) => {
66+ it ( 'filters to packages with single keyword' , async ( ) => {
4667 const packages = ref ( [
4768 createPackage ( { name : 'pkg-a' , keywords : [ 'react' , 'hooks' ] } ) ,
4869 createPackage ( { name : 'pkg-b' , keywords : [ 'vue' ] } ) ,
4970 ] )
5071
51- const { sortedPackages, addKeyword } = useStructuredFilters ( { packages } )
72+ const { sortedPackages, addKeyword } = await useStructuredFiltersInComponent ( packages )
5273 addKeyword ( 'react' )
5374
5475 expect ( sortedPackages . value ) . toHaveLength ( 1 )
5576 expect ( sortedPackages . value [ 0 ] ! . package . name ) . toBe ( 'pkg-a' )
5677 } )
5778
58- it ( 'uses AND logic for multiple keywords' , ( ) => {
79+ it ( 'uses AND logic for multiple keywords' , async ( ) => {
5980 const packages = ref ( [
6081 createPackage ( { name : 'pkg-a' , keywords : [ 'react' , 'hooks' ] } ) ,
6182 createPackage ( { name : 'pkg-b' , keywords : [ 'react' , 'state' ] } ) ,
6283 createPackage ( { name : 'pkg-c' , keywords : [ 'react' , 'hooks' , 'state' ] } ) ,
6384 ] )
6485
65- const { sortedPackages, addKeyword } = useStructuredFilters ( { packages } )
86+ const { sortedPackages, addKeyword } = await useStructuredFiltersInComponent ( packages )
6687 addKeyword ( 'react' )
6788 addKeyword ( 'hooks' )
6889
@@ -72,13 +93,13 @@ describe('useStructuredFilters', () => {
7293 expect ( sortedPackages . value . map ( p => p . package . name ) ) . toContain ( 'pkg-c' )
7394 } )
7495
75- it ( 'returns empty when no package has all keywords' , ( ) => {
96+ it ( 'returns empty when no package has all keywords' , async ( ) => {
7697 const packages = ref ( [
7798 createPackage ( { name : 'pkg-a' , keywords : [ 'react' ] } ) ,
7899 createPackage ( { name : 'pkg-b' , keywords : [ 'hooks' ] } ) ,
79100 ] )
80101
81- const { sortedPackages, addKeyword } = useStructuredFilters ( { packages } )
102+ const { sortedPackages, addKeyword } = await useStructuredFiltersInComponent ( packages )
82103 addKeyword ( 'react' )
83104 addKeyword ( 'hooks' )
84105
@@ -87,56 +108,60 @@ describe('useStructuredFilters', () => {
87108 } )
88109
89110 describe ( 'text filtering' , ( ) => {
90- it ( 'filters by name when scope is name' , ( ) => {
111+ it ( 'filters by name when scope is name' , async ( ) => {
91112 const packages = ref ( [
92113 createPackage ( { name : 'react-query' , description : 'Data fetching' } ) ,
93114 createPackage ( { name : 'vue-query' , description : 'Data fetching for Vue' } ) ,
94115 ] )
95116
96- const { sortedPackages, setTextFilter, setSearchScope } = useStructuredFilters ( { packages } )
117+ const { sortedPackages, setTextFilter, setSearchScope } =
118+ await useStructuredFiltersInComponent ( packages )
97119 setSearchScope ( 'name' )
98120 setTextFilter ( 'react' )
99121
100122 expect ( sortedPackages . value ) . toHaveLength ( 1 )
101123 expect ( sortedPackages . value [ 0 ] ! . package . name ) . toBe ( 'react-query' )
102124 } )
103125
104- it ( 'filters by description when scope is description' , ( ) => {
126+ it ( 'filters by description when scope is description' , async ( ) => {
105127 const packages = ref ( [
106128 createPackage ( { name : 'pkg-a' , description : 'A React component library' } ) ,
107129 createPackage ( { name : 'pkg-b' , description : 'A Vue component library' } ) ,
108130 ] )
109131
110- const { sortedPackages, setTextFilter, setSearchScope } = useStructuredFilters ( { packages } )
132+ const { sortedPackages, setTextFilter, setSearchScope } =
133+ await useStructuredFiltersInComponent ( packages )
111134 setSearchScope ( 'description' )
112135 setTextFilter ( 'React' )
113136
114137 expect ( sortedPackages . value ) . toHaveLength ( 1 )
115138 expect ( sortedPackages . value [ 0 ] ! . package . name ) . toBe ( 'pkg-a' )
116139 } )
117140
118- it ( 'filters by keywords when scope is keywords' , ( ) => {
141+ it ( 'filters by keywords when scope is keywords' , async ( ) => {
119142 const packages = ref ( [
120143 createPackage ( { name : 'pkg-a' , keywords : [ 'typescript' , 'react' ] } ) ,
121144 createPackage ( { name : 'pkg-b' , keywords : [ 'javascript' , 'vue' ] } ) ,
122145 ] )
123146
124- const { sortedPackages, setTextFilter, setSearchScope } = useStructuredFilters ( { packages } )
147+ const { sortedPackages, setTextFilter, setSearchScope } =
148+ await useStructuredFiltersInComponent ( packages )
125149 setSearchScope ( 'keywords' )
126150 setTextFilter ( 'type' )
127151
128152 expect ( sortedPackages . value ) . toHaveLength ( 1 )
129153 expect ( sortedPackages . value [ 0 ] ! . package . name ) . toBe ( 'pkg-a' )
130154 } )
131155
132- it ( 'filters by all fields when scope is all' , ( ) => {
156+ it ( 'filters by all fields when scope is all' , async ( ) => {
133157 const packages = ref ( [
134158 createPackage ( { name : 'pkg-a' , description : 'foo' , keywords : [ 'bar' ] } ) ,
135159 createPackage ( { name : 'pkg-b' , description : 'baz' , keywords : [ 'qux' ] } ) ,
136160 createPackage ( { name : 'search-term' , description : 'other' , keywords : [ ] } ) ,
137161 ] )
138162
139- const { sortedPackages, setTextFilter, setSearchScope } = useStructuredFilters ( { packages } )
163+ const { sortedPackages, setTextFilter, setSearchScope } =
164+ await useStructuredFiltersInComponent ( packages )
140165 setSearchScope ( 'all' )
141166 setTextFilter ( 'search-term' )
142167
@@ -146,71 +171,76 @@ describe('useStructuredFilters', () => {
146171 } )
147172
148173 describe ( 'text filtering with operators' , ( ) => {
149- it ( 'parses name: operator in all scope' , ( ) => {
174+ it ( 'parses name: operator in all scope' , async ( ) => {
150175 const packages = ref ( [
151176 createPackage ( { name : 'react-query' , description : 'vue stuff' } ) ,
152177 createPackage ( { name : 'vue-query' , description : 'react stuff' } ) ,
153178 ] )
154179
155- const { sortedPackages, setTextFilter, setSearchScope } = useStructuredFilters ( { packages } )
180+ const { sortedPackages, setTextFilter, setSearchScope } =
181+ await useStructuredFiltersInComponent ( packages )
156182 setSearchScope ( 'all' )
157183 setTextFilter ( 'name:react' )
158184
159185 expect ( sortedPackages . value ) . toHaveLength ( 1 )
160186 expect ( sortedPackages . value [ 0 ] ! . package . name ) . toBe ( 'react-query' )
161187 } )
162188
163- it ( 'parses desc: operator in all scope' , ( ) => {
189+ it ( 'parses desc: operator in all scope' , async ( ) => {
164190 const packages = ref ( [
165191 createPackage ( { name : 'pkg-a' , description : 'A fantastic library' } ) ,
166192 createPackage ( { name : 'pkg-b' , description : 'A mediocre library' } ) ,
167193 ] )
168194
169- const { sortedPackages, setTextFilter, setSearchScope } = useStructuredFilters ( { packages } )
195+ const { sortedPackages, setTextFilter, setSearchScope } =
196+ await useStructuredFiltersInComponent ( packages )
170197 setSearchScope ( 'all' )
171198 setTextFilter ( 'desc:fantastic' )
172199
173200 expect ( sortedPackages . value ) . toHaveLength ( 1 )
174201 expect ( sortedPackages . value [ 0 ] ! . package . name ) . toBe ( 'pkg-a' )
175202 } )
176203
177- it ( 'parses kw: operator in all scope' , ( ) => {
204+ it ( 'parses kw: operator in all scope' , async ( ) => {
178205 const packages = ref ( [
179206 createPackage ( { name : 'pkg-a' , keywords : [ 'typescript' ] } ) ,
180207 createPackage ( { name : 'pkg-b' , keywords : [ 'javascript' ] } ) ,
181208 ] )
182209
183- const { sortedPackages, setTextFilter, setSearchScope } = useStructuredFilters ( { packages } )
210+ const { sortedPackages, setTextFilter, setSearchScope } =
211+ await useStructuredFiltersInComponent ( packages )
184212 setSearchScope ( 'all' )
185213 setTextFilter ( 'kw:typescript' )
186214
187215 expect ( sortedPackages . value ) . toHaveLength ( 1 )
188216 expect ( sortedPackages . value [ 0 ] ! . package . name ) . toBe ( 'pkg-a' )
189217 } )
190218
191- it ( 'combines multiple operators with AND logic' , ( ) => {
219+ it ( 'combines multiple operators with AND logic' , async ( ) => {
192220 const packages = ref ( [
193221 createPackage ( { name : 'react-lib' , description : 'hooks library' , keywords : [ 'react' ] } ) ,
194222 createPackage ( { name : 'react-other' , description : 'other stuff' , keywords : [ 'react' ] } ) ,
195223 createPackage ( { name : 'vue-lib' , description : 'hooks library' , keywords : [ 'vue' ] } ) ,
196224 ] )
197225
198- const { sortedPackages, setTextFilter, setSearchScope } = useStructuredFilters ( { packages } )
226+ const { sortedPackages, setTextFilter, setSearchScope } =
227+ await useStructuredFiltersInComponent ( packages )
199228 setSearchScope ( 'all' )
200229 setTextFilter ( 'name:react desc:hooks' )
201230
202231 expect ( sortedPackages . value ) . toHaveLength ( 1 )
203232 expect ( sortedPackages . value [ 0 ] ! . package . name ) . toBe ( 'react-lib' )
204233 } )
205234
206- it ( 'handles comma-separated keyword values with OR logic' , ( ) => {
235+ it ( 'handles comma-separated keyword values with OR logic' , async ( ) => {
207236 const packages = ref ( [
208237 createPackage ( { name : 'pkg-a' , keywords : [ 'react' ] } ) ,
209238 createPackage ( { name : 'pkg-b' , keywords : [ 'vue' ] } ) ,
210239 createPackage ( { name : 'pkg-c' , keywords : [ 'angular' ] } ) ,
211240 ] )
212241
213- const { sortedPackages, setTextFilter, setSearchScope } = useStructuredFilters ( { packages } )
242+ const { sortedPackages, setTextFilter, setSearchScope } =
243+ await useStructuredFiltersInComponent ( packages )
214244 setSearchScope ( 'all' )
215245 setTextFilter ( 'kw:react,vue' )
216246
@@ -219,7 +249,7 @@ describe('useStructuredFilters', () => {
219249 expect ( sortedPackages . value . map ( p => p . package . name ) ) . toContain ( 'pkg-b' )
220250 } )
221251
222- it ( 'combines operators with remaining text' , ( ) => {
252+ it ( 'combines operators with remaining text' , async ( ) => {
223253 const packages = ref ( [
224254 createPackage ( {
225255 name : 'react-query' ,
@@ -234,7 +264,8 @@ describe('useStructuredFilters', () => {
234264 } ) ,
235265 ] )
236266
237- const { sortedPackages, setTextFilter, setSearchScope } = useStructuredFilters ( { packages } )
267+ const { sortedPackages, setTextFilter, setSearchScope } =
268+ await useStructuredFiltersInComponent ( packages )
238269 setSearchScope ( 'all' )
239270 setTextFilter ( 'name:react fetching' )
240271
@@ -244,26 +275,26 @@ describe('useStructuredFilters', () => {
244275 } )
245276
246277 describe ( 'download range filtering' , ( ) => {
247- it ( 'filters packages below threshold' , ( ) => {
278+ it ( 'filters packages below threshold' , async ( ) => {
248279 const packages = ref ( [
249280 createPackage ( { name : 'popular' , downloads : 50000 } ) ,
250281 createPackage ( { name : 'unpopular' , downloads : 50 } ) ,
251282 ] )
252283
253- const { sortedPackages, setDownloadRange } = useStructuredFilters ( { packages } )
284+ const { sortedPackages, setDownloadRange } = await useStructuredFiltersInComponent ( packages )
254285 setDownloadRange ( 'gt100k' )
255286
256287 expect ( sortedPackages . value ) . toHaveLength ( 0 )
257288 } )
258289
259- it ( 'filters packages within range' , ( ) => {
290+ it ( 'filters packages within range' , async ( ) => {
260291 const packages = ref ( [
261292 createPackage ( { name : 'pkg-a' , downloads : 500 } ) ,
262293 createPackage ( { name : 'pkg-b' , downloads : 5000 } ) ,
263294 createPackage ( { name : 'pkg-c' , downloads : 50000 } ) ,
264295 ] )
265296
266- const { sortedPackages, setDownloadRange } = useStructuredFilters ( { packages } )
297+ const { sortedPackages, setDownloadRange } = await useStructuredFiltersInComponent ( packages )
267298 setDownloadRange ( '1k-10k' )
268299
269300 expect ( sortedPackages . value ) . toHaveLength ( 1 )
@@ -272,59 +303,59 @@ describe('useStructuredFilters', () => {
272303 } )
273304
274305 describe ( 'sorting' , ( ) => {
275- it ( 'sorts by downloads descending by default with downloads sort' , ( ) => {
306+ it ( 'sorts by downloads descending by default with downloads sort' , async ( ) => {
276307 const packages = ref ( [
277308 createPackage ( { name : 'pkg-a' , downloads : 100 } ) ,
278309 createPackage ( { name : 'pkg-b' , downloads : 1000 } ) ,
279310 createPackage ( { name : 'pkg-c' , downloads : 500 } ) ,
280311 ] )
281312
282- const { sortedPackages, setSort } = useStructuredFilters ( { packages } )
313+ const { sortedPackages, setSort } = await useStructuredFiltersInComponent ( packages )
283314 setSort ( 'downloads-week-desc' )
284315
285316 expect ( sortedPackages . value [ 0 ] ! . package . name ) . toBe ( 'pkg-b' )
286317 expect ( sortedPackages . value [ 1 ] ! . package . name ) . toBe ( 'pkg-c' )
287318 expect ( sortedPackages . value [ 2 ] ! . package . name ) . toBe ( 'pkg-a' )
288319 } )
289320
290- it ( 'sorts by name ascending' , ( ) => {
321+ it ( 'sorts by name ascending' , async ( ) => {
291322 const packages = ref ( [
292323 createPackage ( { name : 'zlib' } ) ,
293324 createPackage ( { name : 'axios' } ) ,
294325 createPackage ( { name : 'lodash' } ) ,
295326 ] )
296327
297- const { sortedPackages, setSort } = useStructuredFilters ( { packages } )
328+ const { sortedPackages, setSort } = await useStructuredFiltersInComponent ( packages )
298329 setSort ( 'name-asc' )
299330
300331 expect ( sortedPackages . value [ 0 ] ! . package . name ) . toBe ( 'axios' )
301332 expect ( sortedPackages . value [ 1 ] ! . package . name ) . toBe ( 'lodash' )
302333 expect ( sortedPackages . value [ 2 ] ! . package . name ) . toBe ( 'zlib' )
303334 } )
304335
305- it ( 'sorts by updated date descending' , ( ) => {
336+ it ( 'sorts by updated date descending' , async ( ) => {
306337 const packages = ref ( [
307338 createPackage ( { name : 'old' , updated : '2023-01-01T00:00:00.000Z' } ) ,
308339 createPackage ( { name : 'new' , updated : '2024-06-01T00:00:00.000Z' } ) ,
309340 createPackage ( { name : 'mid' , updated : '2024-01-01T00:00:00.000Z' } ) ,
310341 ] )
311342
312- const { sortedPackages, setSort } = useStructuredFilters ( { packages } )
343+ const { sortedPackages, setSort } = await useStructuredFiltersInComponent ( packages )
313344 setSort ( 'updated-desc' )
314345
315346 expect ( sortedPackages . value [ 0 ] ! . package . name ) . toBe ( 'new' )
316347 expect ( sortedPackages . value [ 1 ] ! . package . name ) . toBe ( 'mid' )
317348 expect ( sortedPackages . value [ 2 ] ! . package . name ) . toBe ( 'old' )
318349 } )
319350
320- it ( 'relevance sort preserves original order' , ( ) => {
351+ it ( 'relevance sort preserves original order' , async ( ) => {
321352 const packages = ref ( [
322353 createPackage ( { name : 'first' , downloads : 100 } ) ,
323354 createPackage ( { name : 'second' , downloads : 1000 } ) ,
324355 createPackage ( { name : 'third' , downloads : 500 } ) ,
325356 ] )
326357
327- const { sortedPackages, setSort } = useStructuredFilters ( { packages } )
358+ const { sortedPackages, setSort } = await useStructuredFiltersInComponent ( packages )
328359 setSort ( 'relevance-desc' )
329360
330361 // Relevance should preserve the original order from the server
@@ -335,14 +366,14 @@ describe('useStructuredFilters', () => {
335366 } )
336367
337368 describe ( 'clearing filters' , ( ) => {
338- it ( 'clearAllFilters resets all filters' , ( ) => {
369+ it ( 'clearAllFilters resets all filters' , async ( ) => {
339370 const packages = ref ( [
340371 createPackage ( { name : 'pkg-a' , keywords : [ 'react' ] , downloads : 1000 } ) ,
341372 createPackage ( { name : 'pkg-b' , keywords : [ 'vue' ] , downloads : 500 } ) ,
342373 ] )
343374
344375 const { sortedPackages, setTextFilter, addKeyword, setDownloadRange, clearAllFilters } =
345- useStructuredFilters ( { packages } )
376+ await useStructuredFiltersInComponent ( packages )
346377
347378 setTextFilter ( 'pkg-a' )
348379 addKeyword ( 'react' )
@@ -355,13 +386,14 @@ describe('useStructuredFilters', () => {
355386 expect ( sortedPackages . value ) . toHaveLength ( 2 )
356387 } )
357388
358- it ( 'removeKeyword removes specific keyword' , ( ) => {
389+ it ( 'removeKeyword removes specific keyword' , async ( ) => {
359390 const packages = ref ( [
360391 createPackage ( { name : 'pkg-a' , keywords : [ 'react' , 'hooks' ] } ) ,
361392 createPackage ( { name : 'pkg-b' , keywords : [ 'react' ] } ) ,
362393 ] )
363394
364- const { sortedPackages, addKeyword, removeKeyword } = useStructuredFilters ( { packages } )
395+ const { sortedPackages, addKeyword, removeKeyword } =
396+ await useStructuredFiltersInComponent ( packages )
365397
366398 addKeyword ( 'react' )
367399 addKeyword ( 'hooks' )
0 commit comments