Skip to content

Commit 11f07ab

Browse files
jdaltonclaude
andcommitted
test: add explicit unit tests for string utility functions
Add comprehensive test coverage for string utility functions that were previously only tested indirectly through integration tests. These explicit unit tests improve maintainability and documentation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 62dba82 commit 11f07ab

File tree

1 file changed

+305
-0
lines changed

1 file changed

+305
-0
lines changed

test/strings.test.mts

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import {
4+
isBlank,
5+
isNonEmptyString,
6+
isSemverString,
7+
localeCompare,
8+
lowerName,
9+
lowerNamespace,
10+
lowerVersion,
11+
replaceDashesWithUnderscores,
12+
replaceUnderscoresWithDashes,
13+
trimLeadingSlashes,
14+
} from '../src/strings.js'
15+
16+
describe('String utilities', () => {
17+
describe('isBlank', () => {
18+
it('should return true for empty string', () => {
19+
expect(isBlank('')).toBe(true)
20+
})
21+
22+
it('should return true for string with only spaces', () => {
23+
expect(isBlank(' ')).toBe(true)
24+
})
25+
26+
it('should return true for string with only tabs', () => {
27+
expect(isBlank('\t\t\t')).toBe(true)
28+
})
29+
30+
it('should return true for string with various whitespace characters', () => {
31+
// Space, Tab, Line Feed, Vertical Tab, Form Feed, Carriage Return
32+
expect(isBlank(' \t\n\v\f\r')).toBe(true)
33+
})
34+
35+
it('should return true for No-Break Space (U+00A0)', () => {
36+
expect(isBlank('\u00a0')).toBe(true)
37+
})
38+
39+
it('should return true for Ogham Space Mark (U+1680)', () => {
40+
expect(isBlank('\u1680')).toBe(true)
41+
})
42+
43+
it('should return true for various Em/En spaces (U+2000-U+200A)', () => {
44+
expect(isBlank('\u2000\u2001\u2002\u2003\u2004')).toBe(true)
45+
expect(isBlank('\u2005\u2006\u2007\u2008\u2009\u200a')).toBe(true)
46+
})
47+
48+
it('should return true for Line/Paragraph Separators (U+2028, U+2029)', () => {
49+
expect(isBlank('\u2028\u2029')).toBe(true)
50+
})
51+
52+
it('should return true for Narrow No-Break Space (U+202F)', () => {
53+
expect(isBlank('\u202f')).toBe(true)
54+
})
55+
56+
it('should return true for Medium Mathematical Space (U+205F)', () => {
57+
expect(isBlank('\u205f')).toBe(true)
58+
})
59+
60+
it('should return true for Ideographic Space (U+3000)', () => {
61+
expect(isBlank('\u3000')).toBe(true)
62+
})
63+
64+
it('should return true for Byte Order Mark (U+FEFF)', () => {
65+
expect(isBlank('\ufeff')).toBe(true)
66+
})
67+
68+
it('should return false for non-whitespace characters', () => {
69+
expect(isBlank('a')).toBe(false)
70+
expect(isBlank(' a ')).toBe(false)
71+
expect(isBlank(' text ')).toBe(false)
72+
})
73+
74+
it('should return false for string with mixed whitespace and non-whitespace', () => {
75+
expect(isBlank('\t \n test \r\n')).toBe(false)
76+
})
77+
})
78+
79+
describe('isNonEmptyString', () => {
80+
it('should return true for non-empty strings', () => {
81+
expect(isNonEmptyString('test')).toBe(true)
82+
expect(isNonEmptyString(' ')).toBe(true)
83+
expect(isNonEmptyString('a')).toBe(true)
84+
})
85+
86+
it('should return false for empty string', () => {
87+
expect(isNonEmptyString('')).toBe(false)
88+
})
89+
90+
it('should return false for non-string values', () => {
91+
expect(isNonEmptyString(null)).toBe(false)
92+
expect(isNonEmptyString(undefined)).toBe(false)
93+
expect(isNonEmptyString(123)).toBe(false)
94+
expect(isNonEmptyString({})).toBe(false)
95+
expect(isNonEmptyString([])).toBe(false)
96+
})
97+
})
98+
99+
describe('isSemverString', () => {
100+
it('should return true for valid semver strings', () => {
101+
expect(isSemverString('1.0.0')).toBe(true)
102+
expect(isSemverString('0.0.0')).toBe(true)
103+
expect(isSemverString('1.2.3')).toBe(true)
104+
expect(isSemverString('10.20.30')).toBe(true)
105+
})
106+
107+
it('should return true for semver with prerelease', () => {
108+
expect(isSemverString('1.0.0-alpha')).toBe(true)
109+
expect(isSemverString('1.0.0-alpha.1')).toBe(true)
110+
expect(isSemverString('1.0.0-0.3.7')).toBe(true)
111+
expect(isSemverString('1.0.0-x.7.z.92')).toBe(true)
112+
})
113+
114+
it('should return true for semver with build metadata', () => {
115+
expect(isSemverString('1.0.0+20130313144700')).toBe(true)
116+
expect(isSemverString('1.0.0+exp.sha.5114f85')).toBe(true)
117+
})
118+
119+
it('should return true for semver with prerelease and build metadata', () => {
120+
expect(isSemverString('1.0.0-beta+exp.sha.5114f85')).toBe(true)
121+
expect(isSemverString('1.0.0-alpha.1+001')).toBe(true)
122+
})
123+
124+
it('should return false for invalid semver strings', () => {
125+
expect(isSemverString('1')).toBe(false)
126+
expect(isSemverString('1.2')).toBe(false)
127+
expect(isSemverString('1.2.3.4')).toBe(false)
128+
expect(isSemverString('v1.2.3')).toBe(false)
129+
// Leading zeros not allowed
130+
expect(isSemverString('01.2.3')).toBe(false)
131+
})
132+
133+
it('should return false for non-string values', () => {
134+
expect(isSemverString(null)).toBe(false)
135+
expect(isSemverString(undefined)).toBe(false)
136+
expect(isSemverString(123)).toBe(false)
137+
})
138+
})
139+
140+
describe('localeCompare', () => {
141+
it('should compare strings correctly', () => {
142+
expect(localeCompare('a', 'b')).toBeLessThan(0)
143+
expect(localeCompare('b', 'a')).toBeGreaterThan(0)
144+
expect(localeCompare('a', 'a')).toBe(0)
145+
})
146+
147+
it('should handle case-insensitive comparison', () => {
148+
const result = localeCompare('A', 'a')
149+
// Result depends on locale, but should be consistent
150+
expect(typeof result).toBe('number')
151+
})
152+
153+
it('should reuse Intl.Collator instance on subsequent calls', () => {
154+
// First call initializes the collator
155+
const result1 = localeCompare('test1', 'test2')
156+
// Second call reuses the collator
157+
const result2 = localeCompare('test1', 'test2')
158+
expect(result1).toBe(result2)
159+
})
160+
})
161+
162+
describe('lowerName', () => {
163+
it('should convert name to lowercase', () => {
164+
const purl = { name: 'MyPackage' }
165+
lowerName(purl)
166+
expect(purl.name).toBe('mypackage')
167+
})
168+
169+
it('should handle already lowercase name', () => {
170+
const purl = { name: 'mypackage' }
171+
lowerName(purl)
172+
expect(purl.name).toBe('mypackage')
173+
})
174+
175+
it('should handle mixed case names', () => {
176+
const purl = { name: 'My-Package_Name' }
177+
lowerName(purl)
178+
expect(purl.name).toBe('my-package_name')
179+
})
180+
})
181+
182+
describe('lowerNamespace', () => {
183+
it('should convert namespace to lowercase', () => {
184+
const purl = { namespace: 'MyNamespace' }
185+
lowerNamespace(purl)
186+
expect(purl.namespace).toBe('mynamespace')
187+
})
188+
189+
it('should handle undefined namespace', () => {
190+
const purl: { namespace?: string } = {}
191+
lowerNamespace(purl)
192+
expect(purl.namespace).toBeUndefined()
193+
})
194+
195+
it('should handle already lowercase namespace', () => {
196+
const purl = { namespace: 'mynamespace' }
197+
lowerNamespace(purl)
198+
expect(purl.namespace).toBe('mynamespace')
199+
})
200+
})
201+
202+
describe('lowerVersion', () => {
203+
it('should convert version to lowercase', () => {
204+
const purl = { version: '1.0.0-BETA' }
205+
lowerVersion(purl)
206+
expect(purl.version).toBe('1.0.0-beta')
207+
})
208+
209+
it('should handle undefined version', () => {
210+
const purl: { version?: string } = {}
211+
lowerVersion(purl)
212+
expect(purl.version).toBeUndefined()
213+
})
214+
215+
it('should handle already lowercase version', () => {
216+
const purl = { version: '1.0.0-alpha' }
217+
lowerVersion(purl)
218+
expect(purl.version).toBe('1.0.0-alpha')
219+
})
220+
})
221+
222+
describe('replaceDashesWithUnderscores', () => {
223+
it('should replace all dashes with underscores', () => {
224+
expect(replaceDashesWithUnderscores('my-package-name')).toBe(
225+
'my_package_name',
226+
)
227+
})
228+
229+
it('should handle string without dashes', () => {
230+
expect(replaceDashesWithUnderscores('mypackage')).toBe('mypackage')
231+
})
232+
233+
it('should handle empty string', () => {
234+
expect(replaceDashesWithUnderscores('')).toBe('')
235+
})
236+
237+
it('should handle string with only dashes', () => {
238+
expect(replaceDashesWithUnderscores('---')).toBe('___')
239+
})
240+
241+
it('should handle consecutive dashes', () => {
242+
expect(replaceDashesWithUnderscores('my--package')).toBe('my__package')
243+
})
244+
245+
it('should handle dashes at start and end', () => {
246+
expect(replaceDashesWithUnderscores('-package-')).toBe('_package_')
247+
})
248+
})
249+
250+
describe('replaceUnderscoresWithDashes', () => {
251+
it('should replace all underscores with dashes', () => {
252+
expect(replaceUnderscoresWithDashes('my_package_name')).toBe(
253+
'my-package-name',
254+
)
255+
})
256+
257+
it('should handle string without underscores', () => {
258+
expect(replaceUnderscoresWithDashes('mypackage')).toBe('mypackage')
259+
})
260+
261+
it('should handle empty string', () => {
262+
expect(replaceUnderscoresWithDashes('')).toBe('')
263+
})
264+
265+
it('should handle string with only underscores', () => {
266+
expect(replaceUnderscoresWithDashes('___')).toBe('---')
267+
})
268+
269+
it('should handle consecutive underscores', () => {
270+
expect(replaceUnderscoresWithDashes('my__package')).toBe('my--package')
271+
})
272+
273+
it('should handle underscores at start and end', () => {
274+
expect(replaceUnderscoresWithDashes('_package_')).toBe('-package-')
275+
})
276+
})
277+
278+
describe('trimLeadingSlashes', () => {
279+
it('should remove leading slashes', () => {
280+
expect(trimLeadingSlashes('/path/to/package')).toBe('path/to/package')
281+
expect(trimLeadingSlashes('//path/to/package')).toBe('path/to/package')
282+
expect(trimLeadingSlashes('///path')).toBe('path')
283+
})
284+
285+
it('should handle string without leading slashes', () => {
286+
expect(trimLeadingSlashes('path/to/package')).toBe('path/to/package')
287+
})
288+
289+
it('should handle empty string', () => {
290+
expect(trimLeadingSlashes('')).toBe('')
291+
})
292+
293+
it('should handle string with only slashes', () => {
294+
expect(trimLeadingSlashes('///')).toBe('')
295+
})
296+
297+
it('should preserve trailing slashes', () => {
298+
expect(trimLeadingSlashes('/path/')).toBe('path/')
299+
})
300+
301+
it('should preserve internal slashes', () => {
302+
expect(trimLeadingSlashes('/path/to/package')).toBe('path/to/package')
303+
})
304+
})
305+
})

0 commit comments

Comments
 (0)