import { describe, expect, it } from 'vitest' import { ref } from 'vue' import { getOrbStyleAt, ORB_PALETTE_COUNT, PAPER_AVATAR_NAMES, orbIndexForSeed, orbSvgDataUri, resolveAvatarImage, resolveOrbStyle, terminalMarkSvgDataUri, } from '@/utils/orb' import { useWidgetStyles } from '@/composables/useWidgetStyles' import type { AgentCustomization } from '@/types/widget' const decode = (uri: string) => decodeURIComponent(uri.replace('data:image/svg+xml,', '')) describe('paper-craft avatars', () => { it('ships ten distinct flat vectors under the existing variant indexes', () => { expect(ORB_PALETTE_COUNT).toBe(10) expect(PAPER_AVATAR_NAMES).toHaveLength(10) const variants = Array.from({ length: 10 }, (_, i) => orbSvgDataUri('support', i)) expect(new Set(variants).size).toBe(10) for (const uri of variants) { const svg = new DOMParser().parseFromString(decode(uri), 'image/svg+xml') expect(svg.querySelector('parsererror')).toBeNull() expect(svg.documentElement.getAttribute('viewBox')).toBe('0 0 100 100') expect(svg.querySelector('radialGradient, linearGradient, script, image')).toBeNull() expect(svg.querySelector('path')).not.toBeNull() } }) it('uses the exact same artwork for CSS avatar slots and img slots', () => { for (let i = 0; i < 10; i++) { expect(getOrbStyleAt(i).background).toContain(orbSvgDataUri('support', i)) expect(resolveOrbStyle('support', i)).toEqual(getOrbStyleAt(i)) } }) it('retains stable seeds and normalizes out-of-range stored indexes', () => { expect(orbSvgDataUri('support')).toBe(orbSvgDataUri('', orbIndexForSeed('support'))) expect(orbSvgDataUri('', -1)).toBe(orbSvgDataUri('', 9)) expect(orbSvgDataUri('', 12)).toBe(orbSvgDataUri('', 2)) expect(orbSvgDataUri('', 2.5)).toBe(orbSvgDataUri('', 2)) for (const invalid of [NaN, Infinity, '2', null]) { expect(orbSvgDataUri('support', invalid)).toBe(orbSvgDataUri('support')) } }) it('replaces an old stored orb from metadata, not from the old data URI', () => { expect(resolveAvatarImage('support', { photo_url: 'data:image/svg+xml,old-gradient', customization_metadata: { avatar_style: 'orb', orb_variant: 0 }, })).toBe(orbSvgDataUri('support', 0)) }) it.each([ '/api/v1/uploads/agents/a.png', 'https://cdn.example/a.png?signature=preserved', 'data:image/png;base64,customer-photo', 'blob:https://example.com/preview', ])('preserves an uploaded image: %s', (photo) => { expect(resolveAvatarImage('support', { photo_url: photo })).toBe(photo) expect(resolveAvatarImage('support', { photo_url: photo, customization_metadata: { avatar_style: 'photo', orb_variant: 0 }, })).toBe(photo) }) it('provides a complete avatar when no photo exists', () => { expect(resolveAvatarImage('support')).toBe(orbSvgDataUri('support')) }) it('retains the terminal option and rejects markup in its color', () => { const terminal = terminalMarkSvgDataUri() expect(decode(terminal)).toContain('#8BB49C') expect(terminalMarkSvgDataUri('\"/>')).toBe(terminal) expect(resolveAvatarImage('support', { photo_url: terminal, customization_metadata: { avatar_style: 'terminal' }, })).toBe(terminal) }) it('updates the real widget image reactively and preserves its runtime upload origin', () => { const customization = ref({ photo_url: 'data:image/svg+xml,old-gradient', customization_metadata: { avatar_style: 'orb', orb_variant: 0 }, }) const { photoUrl } = useWidgetStyles(customization, ref('support')) expect(photoUrl.value).toBe(orbSvgDataUri('support', 0)) customization.value = { photo_url: 'https://cdn.example/customer.png', customization_metadata: { avatar_style: 'photo' }, } expect(photoUrl.value).toBe('https://cdn.example/customer.png') }) })