forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1import { describe, expect, it } from 'vitest'
2import { hasSearchOperators, parseSearchOperators } from '~/composables/useStructuredFilters'
3
4describe('parseSearchOperators', () => {
5 describe('basic operator parsing', () => {
6 it('parses name: operator', () => {
7 const result = parseSearchOperators('name:react')
8 expect(result).toEqual({ name: ['react'] })
9 })
10
11 it('parses desc: operator', () => {
12 const result = parseSearchOperators('desc:framework')
13 expect(result).toEqual({ description: ['framework'] })
14 })
15
16 it('parses description: operator (long form)', () => {
17 const result = parseSearchOperators('description:framework')
18 expect(result).toEqual({ description: ['framework'] })
19 })
20
21 it('parses kw: operator', () => {
22 const result = parseSearchOperators('kw:typescript')
23 expect(result).toEqual({ keywords: ['typescript'] })
24 })
25
26 it('parses keyword: operator (long form)', () => {
27 const result = parseSearchOperators('keyword:typescript')
28 expect(result).toEqual({ keywords: ['typescript'] })
29 })
30 })
31
32 describe('comma-separated values', () => {
33 it('parses multiple keywords with comma', () => {
34 const result = parseSearchOperators('kw:typescript,react,hooks')
35 expect(result).toEqual({ keywords: ['typescript', 'react', 'hooks'] })
36 })
37
38 it('parses multiple names with comma', () => {
39 const result = parseSearchOperators('name:react,vue,angular')
40 expect(result).toEqual({ name: ['react', 'vue', 'angular'] })
41 })
42
43 it('handles empty values between commas', () => {
44 const result = parseSearchOperators('kw:foo,,bar')
45 expect(result).toEqual({ keywords: ['foo', 'bar'] })
46 })
47 })
48
49 describe('multiple operators', () => {
50 it('parses name and kw operators together', () => {
51 const result = parseSearchOperators('name:react kw:typescript')
52 expect(result).toEqual({
53 name: ['react'],
54 keywords: ['typescript'],
55 })
56 })
57
58 it('parses all three operator types', () => {
59 const result = parseSearchOperators('name:react desc:framework kw:typescript')
60 expect(result).toEqual({
61 name: ['react'],
62 description: ['framework'],
63 keywords: ['typescript'],
64 })
65 })
66
67 it('merges multiple instances of same operator', () => {
68 const result = parseSearchOperators('kw:react kw:typescript')
69 expect(result).toEqual({
70 keywords: ['react', 'typescript'],
71 })
72 })
73 })
74
75 describe('remaining text', () => {
76 it('captures text without operators', () => {
77 const result = parseSearchOperators('some search text')
78 expect(result).toEqual({ text: 'some search text' })
79 })
80
81 it('captures remaining text after operators', () => {
82 const result = parseSearchOperators('name:react some text')
83 expect(result).toEqual({
84 name: ['react'],
85 text: 'some text',
86 })
87 })
88
89 it('captures remaining text before operators', () => {
90 const result = parseSearchOperators('some text name:react')
91 expect(result).toEqual({
92 name: ['react'],
93 text: 'some text',
94 })
95 })
96
97 it('captures text mixed with operators', () => {
98 const result = parseSearchOperators('hello name:react world kw:hooks foo')
99 expect(result).toEqual({
100 name: ['react'],
101 keywords: ['hooks'],
102 text: 'hello world foo',
103 })
104 })
105
106 it('collapses multiple spaces in remaining text', () => {
107 const result = parseSearchOperators('name:react lots of spaces')
108 expect(result).toEqual({
109 name: ['react'],
110 text: 'lots of spaces',
111 })
112 })
113 })
114
115 describe('case insensitivity', () => {
116 it('handles uppercase operator names', () => {
117 const result = parseSearchOperators('NAME:react')
118 expect(result).toEqual({ name: ['react'] })
119 })
120
121 it('handles mixed case operator names', () => {
122 const result = parseSearchOperators('NaMe:react KW:typescript')
123 expect(result).toEqual({
124 name: ['react'],
125 keywords: ['typescript'],
126 })
127 })
128 })
129
130 describe('edge cases', () => {
131 it('returns empty object for empty string', () => {
132 const result = parseSearchOperators('')
133 expect(result).toEqual({})
134 })
135
136 it('returns empty object for whitespace only', () => {
137 const result = parseSearchOperators(' ')
138 expect(result).toEqual({})
139 })
140
141 it('handles operator with no value', () => {
142 // "name:" followed by space - the regex won't match empty values
143 const result = parseSearchOperators('name: react')
144 expect(result).toEqual({ text: 'name: react' })
145 })
146
147 it('handles special characters in values', () => {
148 const result = parseSearchOperators('name:@scope/package')
149 expect(result).toEqual({ name: ['@scope/package'] })
150 })
151
152 it('handles hyphenated values', () => {
153 const result = parseSearchOperators('kw:gatsby-plugin')
154 expect(result).toEqual({ keywords: ['gatsby-plugin'] })
155 })
156 })
157})
158
159describe('hasSearchOperators', () => {
160 it('returns true when name is present', () => {
161 expect(hasSearchOperators({ name: ['react'] })).toBe(true)
162 })
163
164 it('returns true when description is present', () => {
165 expect(hasSearchOperators({ description: ['framework'] })).toBe(true)
166 })
167
168 it('returns true when keywords is present', () => {
169 expect(hasSearchOperators({ keywords: ['typescript'] })).toBe(true)
170 })
171
172 it('returns false when only text is present', () => {
173 expect(hasSearchOperators({ text: 'search query' })).toBe(false)
174 })
175
176 it('returns false for empty object', () => {
177 expect(hasSearchOperators({})).toBe(false)
178 })
179
180 it('returns true when operators and text are present', () => {
181 expect(hasSearchOperators({ name: ['react'], text: 'query' })).toBe(true)
182 })
183
184 it('returns false for empty arrays', () => {
185 expect(hasSearchOperators({ name: [], keywords: [] })).toBe(false)
186 })
187})