2026-03-13 11:46:18 +00:00
|
|
|
import { describe, expect, test, beforeAll } from 'bun:test'
|
2026-03-11 13:42:05 +00:00
|
|
|
|
|
|
|
|
import { fetchIcon, fetchIcons, searchIcons, searchIconsBatch } from '@open-pencil/core'
|
|
|
|
|
|
2026-05-05 23:59:56 +00:00
|
|
|
import { expectDefined } from '#tests/helpers/assert'
|
|
|
|
|
|
2026-03-13 11:46:18 +00:00
|
|
|
let hasNetwork = true
|
|
|
|
|
beforeAll(async () => {
|
|
|
|
|
try {
|
2026-05-03 18:03:40 +00:00
|
|
|
await fetch('https://api.iconify.design/mdi.json?icons=home', {
|
|
|
|
|
signal: AbortSignal.timeout(3000)
|
|
|
|
|
})
|
2026-03-13 11:46:18 +00:00
|
|
|
} catch {
|
|
|
|
|
hasNetwork = false
|
|
|
|
|
console.warn('⚠ Skipping iconify tests — no network')
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const networkTest: typeof test = (name, fn, timeout) =>
|
2026-05-03 18:03:40 +00:00
|
|
|
test(
|
|
|
|
|
name,
|
|
|
|
|
async () => {
|
|
|
|
|
if (!hasNetwork) return
|
|
|
|
|
await (fn as () => Promise<void>)()
|
|
|
|
|
},
|
|
|
|
|
timeout
|
|
|
|
|
)
|
2026-03-13 11:46:18 +00:00
|
|
|
|
2026-03-11 13:42:05 +00:00
|
|
|
describe('fetchIcon', () => {
|
2026-03-13 11:46:18 +00:00
|
|
|
networkTest('fetches mdi:home', async () => {
|
2026-03-11 13:42:05 +00:00
|
|
|
const icon = await fetchIcon('mdi:home', 24)
|
|
|
|
|
expect(icon.prefix).toBe('mdi')
|
|
|
|
|
expect(icon.name).toBe('home')
|
|
|
|
|
expect(icon.width).toBe(24)
|
|
|
|
|
expect(icon.height).toBe(24)
|
|
|
|
|
expect(icon.paths.length).toBeGreaterThan(0)
|
|
|
|
|
|
|
|
|
|
const path = icon.paths[0]
|
|
|
|
|
expect(path.vectorNetwork.vertices.length).toBeGreaterThan(3)
|
|
|
|
|
expect(path.vectorNetwork.segments.length).toBeGreaterThan(3)
|
|
|
|
|
expect(path.fill).toBe('currentColor')
|
|
|
|
|
expect(path.stroke).toBeNull()
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-13 11:46:18 +00:00
|
|
|
networkTest('fetches lucide:heart (stroked icon)', async () => {
|
2026-03-11 13:42:05 +00:00
|
|
|
const icon = await fetchIcon('lucide:heart', 24)
|
|
|
|
|
expect(icon.paths.length).toBeGreaterThan(0)
|
|
|
|
|
|
|
|
|
|
const path = icon.paths[0]
|
|
|
|
|
expect(path.vectorNetwork.vertices.length).toBeGreaterThan(3)
|
|
|
|
|
expect(path.stroke).toBe('currentColor')
|
|
|
|
|
expect(path.strokeWidth).toBe(2)
|
|
|
|
|
expect(path.strokeCap).toBe('round')
|
|
|
|
|
expect(path.strokeJoin).toBe('round')
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-13 11:46:18 +00:00
|
|
|
networkTest('scales to custom size', async () => {
|
2026-03-11 13:42:05 +00:00
|
|
|
const icon = await fetchIcon('mdi:home', 48)
|
|
|
|
|
expect(icon.width).toBe(48)
|
|
|
|
|
expect(icon.height).toBe(48)
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-13 11:46:18 +00:00
|
|
|
networkTest('caches repeated fetches', async () => {
|
2026-03-11 13:42:05 +00:00
|
|
|
const a = await fetchIcon('mdi:home', 24)
|
|
|
|
|
const b = await fetchIcon('mdi:home', 24)
|
|
|
|
|
expect(a).toBe(b)
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-13 11:46:18 +00:00
|
|
|
networkTest('throws on invalid icon name format', async () => {
|
2026-03-11 13:42:05 +00:00
|
|
|
await expect(fetchIcon('invalid')).rejects.toThrow('prefix:name')
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-13 11:46:18 +00:00
|
|
|
networkTest('throws on non-existent icon', async () => {
|
2026-05-03 18:03:40 +00:00
|
|
|
await expect(fetchIcon('mdi:this-icon-definitely-does-not-exist-xyz123')).rejects.toThrow(
|
|
|
|
|
'not found'
|
|
|
|
|
)
|
2026-03-11 13:42:05 +00:00
|
|
|
})
|
|
|
|
|
|
2026-03-13 11:46:18 +00:00
|
|
|
networkTest('heroicons:eye — multi-path icon with group attrs', async () => {
|
2026-03-11 13:42:05 +00:00
|
|
|
const icon = await fetchIcon('heroicons:eye', 24)
|
|
|
|
|
expect(icon.paths.length).toBeGreaterThanOrEqual(2)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('fetchIcons (batch)', () => {
|
2026-03-13 11:46:18 +00:00
|
|
|
networkTest('fetches multiple icons from same prefix in one request', async () => {
|
2026-03-11 13:42:05 +00:00
|
|
|
const icons = await fetchIcons(['lucide:star', 'lucide:home', 'lucide:search'], 24)
|
|
|
|
|
expect(icons.size).toBe(3)
|
|
|
|
|
expect(icons.get('lucide:star')).toBeDefined()
|
|
|
|
|
expect(icons.get('lucide:home')).toBeDefined()
|
|
|
|
|
expect(icons.get('lucide:search')).toBeDefined()
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-13 11:46:18 +00:00
|
|
|
networkTest('fetches icons across multiple prefixes', async () => {
|
2026-03-11 13:42:05 +00:00
|
|
|
const icons = await fetchIcons(['mdi:heart', 'lucide:heart'], 24)
|
|
|
|
|
expect(icons.size).toBe(2)
|
|
|
|
|
expect(icons.get('mdi:heart')?.prefix).toBe('mdi')
|
|
|
|
|
expect(icons.get('lucide:heart')?.prefix).toBe('lucide')
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-13 11:46:18 +00:00
|
|
|
networkTest('skips non-existent icons without failing', async () => {
|
2026-03-11 13:42:05 +00:00
|
|
|
const icons = await fetchIcons(['mdi:home', 'mdi:nonexistent-xyz-999'], 24)
|
|
|
|
|
expect(icons.has('mdi:home')).toBe(true)
|
|
|
|
|
expect(icons.has('mdi:nonexistent-xyz-999')).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-13 11:46:18 +00:00
|
|
|
networkTest('populates cache for subsequent fetchIcon calls', async () => {
|
2026-03-11 13:42:05 +00:00
|
|
|
await fetchIcons(['mdi:star'], 24)
|
|
|
|
|
const icon = await fetchIcon('mdi:star', 24)
|
|
|
|
|
expect(icon.prefix).toBe('mdi')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('searchIcons', () => {
|
2026-03-13 11:46:18 +00:00
|
|
|
networkTest('searches for heart icons', async () => {
|
2026-03-11 13:42:05 +00:00
|
|
|
const result = await searchIcons('heart', { limit: 10 })
|
|
|
|
|
expect(result.icons.length).toBeGreaterThan(0)
|
|
|
|
|
expect(result.total).toBeGreaterThan(0)
|
|
|
|
|
expect(result.icons[0]).toContain(':')
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-13 11:46:18 +00:00
|
|
|
networkTest('filters by prefix', async () => {
|
2026-03-11 13:42:05 +00:00
|
|
|
const result = await searchIcons('home', { limit: 5, prefix: 'lucide' })
|
|
|
|
|
for (const icon of result.icons) {
|
|
|
|
|
expect(icon.startsWith('lucide:')).toBe(true)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('searchIconsBatch', () => {
|
2026-03-13 11:46:18 +00:00
|
|
|
networkTest('searches multiple queries in parallel', async () => {
|
2026-03-11 13:42:05 +00:00
|
|
|
const results = await searchIconsBatch(['heart', 'arrow'], { limit: 5 })
|
|
|
|
|
expect(results.size).toBe(2)
|
2026-05-05 23:59:56 +00:00
|
|
|
expect(expectDefined(results.get('heart'), 'heart results').icons.length).toBeGreaterThan(0)
|
|
|
|
|
expect(expectDefined(results.get('arrow'), 'arrow results').icons.length).toBeGreaterThan(0)
|
2026-03-11 13:42:05 +00:00
|
|
|
})
|
|
|
|
|
})
|