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.
88 lines
3.0 KiB
TypeScript
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)
|
|
})
|
|
})
|