92 lines
2.5 KiB
TypeScript
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');
|
|
});
|
|
});
|