Files
Genarrative/src/components/CharacterAnimator.test.tsx
kdletters cbc27bad4a
Some checks failed
CI / verify (push) Has been cancelled
init with react+axum+spacetimedb
2026-04-26 18:06:23 +08:00

92 lines
2.5 KiB
TypeScript

// @vitest-environment jsdom
import { render, screen } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { AnimationState, type Character } from '../types';
import { CharacterAnimator } from './CharacterAnimator';
vi.mock('../hooks/useResolvedAssetReadUrl', () => ({
useResolvedAssetReadUrl: vi.fn((source: string | null | undefined) => ({
resolvedUrl: source?.trim() ?? '',
isResolving: false,
shouldResolve: false,
})),
}));
function buildCharacter(overrides: Partial<Character> = {}): Character {
return {
id: 'generated-role',
name: '沈砺',
title: '守灯人',
description: '',
backstory: '',
avatar: '/generated/portrait.png',
portrait: '/generated/portrait.png',
assetFolder: 'custom-world',
assetVariant: 'generated',
attributes: {} as Character['attributes'],
personality: '',
skills: [],
adventureOpenings: {},
...overrides,
};
}
describe('CharacterAnimator portrait fallbacks', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('keeps idle fallback static on the portrait when idle animation is missing', () => {
render(
<CharacterAnimator
state={AnimationState.IDLE}
character={buildCharacter()}
/>,
);
const image = screen.getByRole('img', {
name: /沈砺 idle animation/i,
}) as HTMLImageElement;
expect(image.getAttribute('src')).toBe('/generated/portrait.png');
expect(image.style.transform).toBe('');
});
it('uses a fallen portrait fallback when death animation is missing', () => {
render(
<CharacterAnimator
state={AnimationState.DIE}
character={buildCharacter()}
/>,
);
const image = screen.getByRole('img', {
name: /沈砺 die animation/i,
}) as HTMLImageElement;
expect(image.getAttribute('src')).toBe('/generated/portrait.png');
expect(image.style.animation).toContain(
'character-animator-portrait-death-fall',
);
expect(image.style.transform).toContain('rotate(-90deg)');
expect(image.style.transform).toContain('scaleX(-1)');
});
it('uses generated portrait for movement when generated animation is missing', () => {
render(
<CharacterAnimator
state={AnimationState.RUN}
character={buildCharacter({generatedVisualAssetId: 'assetobj-role-main'})}
/>,
);
const image = screen.getByRole('img', {
name: /沈砺 run animation/i,
}) as HTMLImageElement;
expect(image.getAttribute('src')).toBe('/generated/portrait.png');
});
});