bible-strong-avatar-lab/src/features/avatar/__tests__/avatars-test.ts
smontlouis 2386b78817 feat(runtime): unify React and ESM avatar exports
Add shared core-backed React and browser runtimes, continuous playback transitions, export previews and API documentation. Package the JavaScript export as a CDN-powered demo with the portable avatar definition.
2026-08-17 17:27:20 +02:00

88 lines
3.0 KiB
TypeScript

import { defaultExpression } from '@/features/avatar/presets'
import { createInitialSequences } from '@/features/animation/sequences'
import {
applyAvatarEyeDefaults,
cloneAvatarBehavior,
createAvatar,
createUnkeyedExpressionCopy,
defaultAvatarEyes,
parseAvatarEyeDefaults,
parseAvatarRenderStyle,
resolveAvatarBehavior,
} from '@/features/avatar/avatars'
import { initialExpressions } from '@/features/avatar/presets'
describe('avatar eye defaults', () => {
it('clears the public semantic key when creating custom content from a preset', () => {
const source = { ...defaultExpression, semanticKey: 'attentive-left' }
const copy = createUnkeyedExpressionCopy(source, 'expression-copy')
expect(copy.id).toBe('expression-copy')
expect(copy.semanticKey).toBeUndefined()
expect(source.semanticKey).toBe('attentive-left')
})
it('keeps the historical rendering when using default values', () => {
expect(applyAvatarEyeDefaults(defaultExpression, defaultAvatarEyes)).toEqual(defaultExpression)
})
it('composes avatar defaults as variations around the neutral expression', () => {
const expression = { ...defaultExpression, widthLeft: 28, positionYLeft: 5 }
const eyes = { ...defaultAvatarEyes, widthLeft: 30, positionYLeft: -12 }
const result = applyAvatarEyeDefaults(expression, eyes)
expect(result.widthLeft).toBe(38)
expect(result.positionYLeft).toBe(0)
expect(expression.widthLeft).toBe(28)
})
it('sanitizes partial persisted values', () => {
const result = parseAvatarEyeDefaults({ widthLeft: 42, heightRight: Number.NaN })
expect(result.widthLeft).toBe(42)
expect(result.heightRight).toBe(defaultAvatarEyes.heightRight)
expect(result.spacing).toBe(defaultAvatarEyes.spacing)
})
})
describe('avatar render style', () => {
it('keeps vector rendering as the compatible default', () => {
expect(parseAvatarRenderStyle(undefined)).toEqual({ type: 'vector' })
})
it('falls back to vector rendering while pixel mode is disabled', () => {
expect(
parseAvatarRenderStyle({
type: 'pixel',
resolution: 500,
})
).toEqual({ type: 'vector' })
expect(parseAvatarRenderStyle({ type: 'pixel', resolution: 1 })).toEqual({ type: 'vector' })
})
})
describe('avatar behavior library', () => {
const base = {
expressions: initialExpressions,
sequences: createInitialSequences(),
}
it('inherits the base library until the avatar owns a customization', () => {
const avatar = createAvatar('Strobi')
expect(resolveAvatarBehavior(avatar, base)).toBe(base)
})
it('clones expressions, animations and nested steps as one independent library', () => {
const behavior = cloneAvatarBehavior(base)
expect(behavior).not.toBe(base)
expect(behavior.expressions).not.toBe(base.expressions)
expect(behavior.sequences).not.toBe(base.sequences)
expect(behavior.sequences[0].steps).not.toBe(base.sequences[0].steps)
expect(behavior.sequences[0].blink).not.toBe(base.sequences[0].blink)
})
})